Skip to main content

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::empty);
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).await
134}
135
136/// Options:
137/// - `fullnode`: Optionally sync and execute on a fullnode as well
138pub async fn submit_and_execute_with_options(
139    authority: &AuthorityState,
140    fullnode: Option<&AuthorityState>,
141    transaction: Transaction,
142) -> Result<(VerifiedExecutableTransaction, SignedTransactionEffects), SuiError> {
143    let (exec, effects, _) =
144        submit_and_execute_with_error(authority, fullnode, transaction).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) -> Result<
154    (
155        VerifiedExecutableTransaction,
156        SignedTransactionEffects,
157        Option<ExecutionError>,
158    ),
159    SuiError,
160> {
161    let epoch_store = authority.load_epoch_store_one_call_per_task();
162
163    // Vote on the transaction.
164    let verified_tx = vote_transaction(authority, transaction)?;
165
166    // Create executable - transaction is now certified by consensus
167    let executable =
168        VerifiedExecutableTransaction::new_from_consensus(verified_tx, epoch_store.epoch());
169
170    // This also assigns the accumulator root's version when accumulators are enabled, even if
171    // the transaction has no shared inputs. So we should always call this, whether or not there
172    // are shared objects present in the transaction.
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    let assigned_versions = versions
180        .into_map()
181        .get(&executable.key())
182        .cloned()
183        .unwrap_or_else(AssignedVersions::empty);
184
185    // State accumulator for validation
186    let state_acc =
187        GlobalStateHasher::new_for_tests(authority.get_global_state_hash_store().clone());
188    let include_wrapped_tombstone = !authority
189        .epoch_store_for_testing()
190        .protocol_config()
191        .simplified_unwrap_then_delete();
192    let mut state =
193        state_acc.accumulate_cached_live_object_set_for_testing(include_wrapped_tombstone);
194
195    // Execute
196    let env = ExecutionEnv::new().with_assigned_versions(assigned_versions.clone());
197    let (result, mut execution_error_opt) = authority
198        .try_execute_executable_for_test(&executable, env.clone())
199        .await;
200
201    // Validate state accumulation
202    let state_after =
203        state_acc.accumulate_cached_live_object_set_for_testing(include_wrapped_tombstone);
204    let effects_acc = state_acc.accumulate_effects(
205        &[result.inner().data().clone()],
206        epoch_store.protocol_config(),
207    );
208    state.union(&effects_acc);
209    assert_eq!(state_after.digest(), state.digest());
210
211    // Execute on fullnode if provided, use its error which includes source error
212    if let Some(fullnode) = fullnode {
213        let (_, fullnode_execution_error_opt) = fullnode
214            .try_execute_executable_for_test(&executable, env)
215            .await;
216        execution_error_opt = fullnode_execution_error_opt;
217    }
218
219    Ok((executable, result.into_inner(), execution_error_opt))
220}
221
222/// Enqueues multiple transactions for execution after they've been through consensus.
223pub async fn enqueue_and_execute_all(
224    authority: &AuthorityState,
225    executables: Vec<(VerifiedExecutableTransaction, ExecutionEnv)>,
226) -> Result<Vec<TransactionEffects>, SuiError> {
227    authority.execution_scheduler.enqueue(
228        executables
229            .iter()
230            .map(|(exec, env)| (exec.clone().into(), env.clone()))
231            .collect(),
232        &authority.epoch_store_for_testing(),
233    );
234    let mut output = Vec::new();
235    for (exec, _) in executables {
236        let effects = authority
237            .notify_read_effects_for_testing("", *exec.digest())
238            .await;
239        output.push(effects);
240    }
241    Ok(output)
242}
243
244/// Submits a transaction to consensus and schedules for execution.
245/// Returns assigned versions. Execution happens asynchronously.
246pub async fn submit_and_schedule(
247    authority: &AuthorityState,
248    transaction: Transaction,
249) -> Result<AssignedVersions, SuiError> {
250    let (executable, versions) = submit_to_consensus(authority, transaction).await?;
251
252    let env = ExecutionEnv::new().with_assigned_versions(versions.clone());
253    authority.execution_scheduler().enqueue_transactions(
254        vec![(executable, env)],
255        &authority.epoch_store_for_testing(),
256    );
257
258    Ok(versions)
259}
260
261pub async fn init_state_validator_with_fullnode() -> (Arc<AuthorityState>, Arc<AuthorityState>) {
262    use sui_types::crypto::get_authority_key_pair;
263
264    let validator = TestAuthorityBuilder::new().build().await;
265    let fullnode_key_pair = get_authority_key_pair().1;
266    let fullnode = TestAuthorityBuilder::new()
267        .with_keypair(&fullnode_key_pair)
268        .build()
269        .await;
270    (validator, fullnode)
271}
272
273pub async fn init_state_with_committee(
274    genesis: &Genesis,
275    authority_key: &AuthorityKeyPair,
276) -> Arc<AuthorityState> {
277    TestAuthorityBuilder::new()
278        .with_genesis_and_keypair(genesis, authority_key)
279        .build()
280        .await
281}
282
283pub async fn init_state_with_ids<I: IntoIterator<Item = (SuiAddress, ObjectID)>>(
284    objects: I,
285) -> Arc<AuthorityState> {
286    let state = TestAuthorityBuilder::new().build().await;
287    for (address, object_id) in objects {
288        let obj = Object::with_id_owner_for_testing(object_id, address);
289        state.insert_genesis_object(obj);
290    }
291    state
292}
293
294pub async fn init_state_with_ids_and_versions<
295    I: IntoIterator<Item = (SuiAddress, ObjectID, SequenceNumber)>,
296>(
297    objects: I,
298) -> Arc<AuthorityState> {
299    let state = TestAuthorityBuilder::new().build().await;
300    for (address, object_id, version) in objects {
301        let obj = Object::with_id_owner_version_for_testing(
302            object_id,
303            version,
304            Owner::AddressOwner(address),
305        );
306        state.insert_genesis_object(obj);
307    }
308    state
309}
310
311pub async fn init_state_with_objects<I: IntoIterator<Item = Object>>(
312    objects: I,
313) -> Arc<AuthorityState> {
314    let dir = tempfile::TempDir::new().unwrap();
315    let network_config = sui_swarm_config::network_config_builder::ConfigBuilder::new(&dir).build();
316    let genesis = network_config.genesis;
317    let keypair = network_config.validator_configs[0]
318        .protocol_key_pair()
319        .copy();
320    init_state_with_objects_and_committee(objects, &genesis, &keypair).await
321}
322
323pub async fn init_state_with_objects_and_committee<I: IntoIterator<Item = Object>>(
324    objects: I,
325    genesis: &Genesis,
326    authority_key: &AuthorityKeyPair,
327) -> Arc<AuthorityState> {
328    let state = init_state_with_committee(genesis, authority_key).await;
329    for o in objects {
330        state.insert_genesis_object(o);
331    }
332    state
333}
334
335pub async fn init_state_with_object_id(
336    address: SuiAddress,
337    object: ObjectID,
338) -> Arc<AuthorityState> {
339    init_state_with_ids(std::iter::once((address, object))).await
340}
341
342pub async fn init_state_with_ids_and_expensive_checks<
343    I: IntoIterator<Item = (SuiAddress, ObjectID)>,
344>(
345    objects: I,
346    config: ExpensiveSafetyCheckConfig,
347) -> Arc<AuthorityState> {
348    let state = TestAuthorityBuilder::new()
349        .with_expensive_safety_checks(config)
350        .build()
351        .await;
352    for (address, object_id) in objects {
353        let obj = Object::with_id_owner_for_testing(object_id, address);
354        state.insert_genesis_object(obj);
355    }
356    state
357}
358
359pub fn init_transfer_transaction(
360    authority_state: &AuthorityState,
361    sender: SuiAddress,
362    secret: &AccountKeyPair,
363    recipient: SuiAddress,
364    object_ref: ObjectRef,
365    gas_object_ref: ObjectRef,
366    gas_budget: u64,
367    gas_price: u64,
368) -> VerifiedTransaction {
369    let data = TransactionData::new_transfer(
370        recipient,
371        FullObjectRef::from_fastpath_ref(object_ref),
372        sender,
373        gas_object_ref,
374        gas_budget,
375        gas_price,
376    );
377    let tx = to_sender_signed_transaction(data, secret);
378    authority_state
379        .epoch_store_for_testing()
380        .verify_transaction_require_no_aliases(tx)
381        .unwrap()
382        .into_tx()
383}
384
385#[cfg(test)]
386pub async fn submit_batch_to_consensus<C>(
387    authority: &AuthorityState,
388    transactions: &[Transaction],
389    consensus_handler: &mut crate::consensus_handler::ConsensusHandler<C>,
390    captured_transactions: &crate::consensus_test_utils::CapturedTransactions,
391) -> (Vec<Schedulable>, HashMap<TransactionKey, AssignedVersions>)
392where
393    C: crate::checkpoints::CheckpointServiceNotify + Send + Sync + 'static,
394{
395    use crate::consensus_test_utils::TestConsensusCommit;
396    use sui_types::messages_consensus::ConsensusTransaction;
397    use sui_types::transaction::PlainTransactionWithClaims;
398
399    let consensus_transactions: Vec<ConsensusTransaction> = transactions
400        .iter()
401        .map(|tx| {
402            ConsensusTransaction::new_user_transaction_v2_message(
403                &authority.name,
404                PlainTransactionWithClaims::no_aliases(tx.clone()),
405            )
406        })
407        .collect();
408
409    let epoch_store = authority.epoch_store_for_testing();
410    let round = epoch_store.get_highest_pending_checkpoint_height() + 1;
411    let timestamp_ms = epoch_store.epoch_start_state().epoch_start_timestamp_ms();
412    let sub_dag_index = 0;
413
414    let commit =
415        TestConsensusCommit::new(consensus_transactions, round, timestamp_ms, sub_dag_index);
416
417    consensus_handler
418        .handle_consensus_commit_for_test(commit)
419        .await;
420
421    tokio::time::sleep(std::time::Duration::from_millis(100)).await;
422
423    let (scheduled_txns, assigned_tx_and_versions) = {
424        let mut captured = captured_transactions.lock();
425        assert!(
426            !captured.is_empty(),
427            "Expected transactions to be scheduled"
428        );
429        let (paired, _) = captured.remove(0);
430        let (schedulables, versions): (Vec<_>, Vec<_>) = paired.into_iter().unzip();
431        let assigned_versions = schedulables
432            .iter()
433            .map(|s| s.key())
434            .zip_debug_eq(versions)
435            .collect();
436        (schedulables, assigned_versions)
437    };
438
439    (scheduled_txns, assigned_tx_and_versions)
440}
441
442pub async fn assign_versions_and_schedule(
443    authority: &AuthorityState,
444    executable: &VerifiedExecutableTransaction,
445) -> AssignedVersions {
446    let assigned_versions = authority
447        .epoch_store_for_testing()
448        .assign_shared_object_versions_for_tests(
449            authority.get_object_cache_reader().as_ref(),
450            std::slice::from_ref(&executable.clone()),
451        )
452        .unwrap();
453
454    let versions = assigned_versions
455        .into_map()
456        .get(&executable.key())
457        .cloned()
458        .unwrap_or_else(AssignedVersions::empty);
459
460    let env = ExecutionEnv::new().with_assigned_versions(versions.clone());
461    authority.execution_scheduler().enqueue_transactions(
462        vec![(executable.clone(), env)],
463        &authority.epoch_store_for_testing(),
464    );
465
466    versions
467}
468
469/// Assigns shared object versions for an executable without scheduling for execution.
470/// This is used when you need version assignment but want to control execution separately.
471pub async fn assign_shared_object_versions(
472    authority: &AuthorityState,
473    executable: &VerifiedExecutableTransaction,
474) -> AssignedVersions {
475    let assigned_versions = authority
476        .epoch_store_for_testing()
477        .assign_shared_object_versions_for_tests(
478            authority.get_object_cache_reader().as_ref(),
479            std::slice::from_ref(&executable.clone()),
480        )
481        .unwrap();
482
483    assigned_versions
484        .into_map()
485        .get(&executable.key())
486        .cloned()
487        .unwrap_or_else(AssignedVersions::empty)
488}