Skip to main content

iota_core/
test_utils.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use std::{sync::Arc, time::Duration};
6
7use fastcrypto::{hash::MultisetHash, traits::KeyPair};
8use iota_protocol_config::ProtocolConfig;
9use iota_sdk_types::{
10    Address, CheckpointSummary, EndOfEpochData, GasCostSummary, Identifier, ObjectId,
11    ObjectReference, Transaction, TransactionDigest,
12    crypto::{Intent, IntentScope},
13};
14use iota_types::{
15    base_types::{AuthorityName, ExecutionDigests, random_object_ref},
16    committee::{Committee, EpochId},
17    crypto::{
18        AccountPrivateKey, AuthorityKeyPair, AuthorityPublicKeyBytes, AuthoritySignInfo,
19        AuthoritySignature, AuthorityStrongQuorumSignInfo, Signer,
20    },
21    effects::{SignedTransactionEffects, TestEffectsBuilder},
22    error::IotaError,
23    message_envelope::Envelope,
24    messages_checkpoint::{CertifiedCheckpointSummary, VerifiedCheckpoint},
25    messages_consensus::ConsensusTransactionKind,
26    signature::VerifyParams,
27    transaction::{
28        CallArg, CertifiedTransaction, SenderSignedTransactionAPI, SignedTransaction,
29        TEST_ONLY_GAS_UNIT_FOR_TRANSFER, TransactionAPI, TransactionEnvelope,
30    },
31    utils::{create_fake_transaction, to_sender_signed_transaction},
32};
33use move_core_types::account_address::AccountAddress;
34use tokio::time::timeout;
35use tracing::{info, warn};
36
37use crate::{
38    authority::{AuthorityState, ExecutionEnv},
39    global_state_hasher::GlobalStateHasher,
40};
41
42const WAIT_FOR_TX_TIMEOUT: Duration = Duration::from_secs(15);
43
44pub async fn send_and_confirm_transaction(
45    authority: &AuthorityState,
46    fullnode: Option<&AuthorityState>,
47    transaction: TransactionEnvelope,
48) -> Result<(CertifiedTransaction, SignedTransactionEffects), IotaError> {
49    // Make the initial request
50    let epoch_store = authority.load_epoch_store_one_call_per_task();
51    transaction.validity_check(&epoch_store.tx_validity_check_context())?;
52    let transaction = epoch_store.verify_transaction(transaction)?;
53    let response = authority
54        .handle_transaction(&epoch_store, transaction.clone())
55        .await?;
56    let vote = response.status.into_signed_for_testing();
57
58    // Collect signatures from a quorum of authorities
59    let committee = authority.clone_committee_for_testing();
60    let certificate = CertifiedTransaction::new(transaction.into_message(), vec![vote], &committee)
61        .unwrap()
62        .try_into_verified_for_testing(
63            &committee,
64            &VerifyParams::from_protocol_config(epoch_store.protocol_config()),
65        )
66        .unwrap();
67
68    // Submit the confirmation. *Now* execution actually happens, and it should fail
69    // when we try to look up our dummy module. we unfortunately don't get a
70    // very descriptive error message, but we can at least see that something went
71    // wrong inside the VM
72    //
73    // We also check the incremental effects of the transaction on the live object
74    // set against GlobalStateHasher for testing and regression detection
75    let state_acc =
76        GlobalStateHasher::new_for_tests(authority.get_global_state_hash_store().clone());
77    let mut state = state_acc.accumulate_cached_live_object_set_for_testing()?;
78    let (result, _execution_error_opt) =
79        authority.try_execute_for_test(&certificate, ExecutionEnv::new())?;
80    let state_after = state_acc.accumulate_cached_live_object_set_for_testing()?;
81    let effects_acc = state_acc.accumulate_effects(&[result.inner().data().clone()]);
82    state.union(&effects_acc);
83
84    assert_eq!(state_after.digest(), state.digest());
85
86    if let Some(fullnode) = fullnode {
87        fullnode.try_execute_for_test(&certificate, ExecutionEnv::new())?;
88    }
89    Ok((certificate.into_inner(), result.into_inner()))
90}
91
92#[cfg(test)]
93pub(crate) fn init_state_parameters_from_rng<R>(
94    rng: &mut R,
95) -> (iota_config::genesis::Genesis, AuthorityKeyPair)
96where
97    R: rand::CryptoRng,
98{
99    let dir = iota_macros::nondeterministic!(tempfile::TempDir::new().unwrap());
100    let network_config = iota_swarm_config::network_config_builder::ConfigBuilder::new(&dir)
101        .rng(rng)
102        .build();
103    let genesis = network_config.genesis;
104    let authority_key = network_config.validator_configs[0]
105        .authority_key_pair()
106        .copy();
107
108    (genesis, authority_key)
109}
110
111pub async fn wait_for_tx(digest: TransactionDigest, state: Arc<AuthorityState>) {
112    match timeout(
113        WAIT_FOR_TX_TIMEOUT,
114        state
115            .get_transaction_cache_reader()
116            .try_notify_read_executed_effects("", &[digest]),
117    )
118    .await
119    {
120        Ok(Ok(_)) => info!(?digest, "digest found"),
121        Ok(Err(e)) => panic!("failed to read effects of digest! {e}"),
122        Err(e) => {
123            warn!(?digest, "digest not found!");
124            panic!("timed out waiting for effects of digest! {e}");
125        }
126    }
127}
128
129pub async fn wait_for_all_txes(digests: Vec<TransactionDigest>, state: Arc<AuthorityState>) {
130    match timeout(
131        WAIT_FOR_TX_TIMEOUT,
132        state
133            .get_transaction_cache_reader()
134            .try_notify_read_executed_effects("", &digests),
135    )
136    .await
137    {
138        Ok(Ok(_)) => info!(?digests, "all digests found"),
139        Ok(Err(e)) => panic!("failed to read effects of digests! {e}"),
140        Err(e) => {
141            warn!(?digests, "some digests not found!");
142            panic!("timed out waiting for effects of digests! {e}");
143        }
144    }
145}
146
147pub fn create_fake_cert_and_effect_digest<'a>(
148    signers: impl Iterator<
149        Item = (
150            &'a AuthorityName,
151            &'a (dyn Signer<AuthoritySignature> + Send + Sync),
152        ),
153    >,
154    committee: &Committee,
155) -> (ExecutionDigests, CertifiedTransaction) {
156    let transaction = create_fake_transaction();
157    let cert = CertifiedTransaction::new(
158        transaction.data().clone(),
159        signers
160            .map(|(name, signer)| {
161                AuthoritySignInfo::new(
162                    committee.epoch,
163                    transaction.data(),
164                    Intent::iota_app(IntentScope::SenderSignedTransaction),
165                    *name,
166                    signer,
167                )
168            })
169            .collect(),
170        committee,
171    )
172    .unwrap();
173    let effects = TestEffectsBuilder::new(transaction.data()).build();
174    (
175        ExecutionDigests::new(*transaction.digest(), effects.digest()),
176        cert,
177    )
178}
179
180pub fn make_transfer_iota_transaction(
181    gas_object: ObjectReference,
182    recipient: Address,
183    amount: Option<u64>,
184    sender: Address,
185    private_key: &AccountPrivateKey,
186    gas_price: u64,
187) -> TransactionEnvelope {
188    let tx = Transaction::new_transfer_iota(
189        recipient,
190        sender,
191        amount,
192        gas_object,
193        gas_price * TEST_ONLY_GAS_UNIT_FOR_TRANSFER,
194        gas_price,
195    );
196    to_sender_signed_transaction(tx, private_key)
197}
198
199pub fn make_pay_iota_transaction(
200    gas_object: ObjectReference,
201    coins: Vec<ObjectReference>,
202    recipients: Vec<Address>,
203    amounts: Vec<u64>,
204    sender: Address,
205    private_key: &AccountPrivateKey,
206    gas_price: u64,
207    gas_budget: u64,
208) -> TransactionEnvelope {
209    let tx = Transaction::new_pay_iota(
210        sender, coins, recipients, amounts, gas_object, gas_budget, gas_price,
211    )
212    .unwrap();
213    to_sender_signed_transaction(tx, private_key)
214}
215
216pub fn make_transfer_object_transaction(
217    object_ref: ObjectReference,
218    gas_object: ObjectReference,
219    sender: Address,
220    private_key: &AccountPrivateKey,
221    recipient: Address,
222    gas_price: u64,
223) -> TransactionEnvelope {
224    let tx = Transaction::new_transfer(
225        recipient,
226        object_ref,
227        sender,
228        gas_object,
229        gas_price * TEST_ONLY_GAS_UNIT_FOR_TRANSFER * 10,
230        gas_price,
231    );
232    to_sender_signed_transaction(tx, private_key)
233}
234
235pub fn make_transfer_object_move_transaction(
236    src: Address,
237    private_key: &AccountPrivateKey,
238    dest: Address,
239    object_ref: ObjectReference,
240    framework_obj_id: ObjectId,
241    gas_object_ref: ObjectReference,
242    gas_budget_in_units: u64,
243    gas_price: u64,
244) -> TransactionEnvelope {
245    let args = vec![
246        CallArg::ImmutableOrOwned(object_ref),
247        CallArg::pure(&AccountAddress::new(dest.into_bytes())),
248    ];
249
250    to_sender_signed_transaction(
251        Transaction::new_move_call(
252            src,
253            framework_obj_id,
254            Identifier::from_static("object_basics"),
255            Identifier::from_static("transfer"),
256            Vec::new(),
257            gas_object_ref,
258            args,
259            gas_budget_in_units * gas_price,
260            gas_price,
261        )
262        .unwrap(),
263        private_key,
264    )
265}
266
267/// Make a dummy tx that uses random object refs.
268pub fn make_dummy_tx(
269    receiver: Address,
270    sender: Address,
271    sender_sec: &AccountPrivateKey,
272) -> TransactionEnvelope {
273    TransactionEnvelope::from_data_and_signer(
274        Transaction::new_transfer(
275            receiver,
276            random_object_ref(),
277            sender,
278            random_object_ref(),
279            TEST_ONLY_GAS_UNIT_FOR_TRANSFER * 10,
280            10,
281        ),
282        vec![sender_sec],
283    )
284}
285
286/// Make a cert using an arbitrarily large committee.
287pub fn make_cert_with_large_committee(
288    committee: &Committee,
289    key_pairs: &[AuthorityKeyPair],
290    transaction: &TransactionEnvelope,
291) -> CertifiedTransaction {
292    // assumes equal weighting.
293    let len = committee.voting_rights.len();
294    assert_eq!(len, key_pairs.len());
295    let count = (len * 2).div_ceil(3);
296
297    let sigs: Vec<_> = key_pairs
298        .iter()
299        .take(count)
300        .map(|key_pair| {
301            SignedTransaction::new(
302                committee.epoch(),
303                transaction.clone().into_data(),
304                key_pair,
305                AuthorityPublicKeyBytes::from(key_pair.public()),
306            )
307            .auth_sig()
308            .clone()
309        })
310        .collect();
311
312    let cert = CertifiedTransaction::new(transaction.clone().into_data(), sigs, committee).unwrap();
313    cert.verify_signatures_authenticated(
314        committee,
315        &VerifyParams::from_protocol_config(&ProtocolConfig::get_for_max_version_UNSAFE()),
316    )
317    .unwrap();
318    cert
319}
320
321/// Selects which scheduler `ExecutionSchedulerWrapper::new` builds. Both
322/// selector variables are set explicitly, so the choice holds regardless of
323/// `DEFAULT_USE_EXECUTION_SCHEDULER`. Call this before building the authority.
324pub fn set_scheduler_env(use_execution_scheduler: bool) {
325    // SAFETY (edition 2021): plain env mutation, no other threads race here.
326    if use_execution_scheduler {
327        std::env::set_var("ENABLE_EXECUTION_SCHEDULER", "1");
328        std::env::remove_var("ENABLE_TRANSACTION_MANAGER");
329    } else {
330        std::env::set_var("ENABLE_TRANSACTION_MANAGER", "1");
331        std::env::remove_var("ENABLE_EXECUTION_SCHEDULER");
332    }
333}
334
335/// The feature flag that gates a consensus transaction kind, or `None` when
336/// the kind is accepted at every protocol version.
337///
338/// `IotaTxValidator` rejects a gated kind before a block is voted for, and
339/// `verify_consensus_transaction` applies the same gate to committed output.
340/// The tests for both layers check them against this one table; the
341/// exhaustive match forces a decision for every new kind.
342#[allow(deprecated)]
343pub fn consensus_transaction_feature_gate(
344    kind: &ConsensusTransactionKind,
345    config: &ProtocolConfig,
346) -> Option<bool> {
347    match kind {
348        ConsensusTransactionKind::CertifiedTransaction(_)
349        | ConsensusTransactionKind::CheckpointSignature(_)
350        | ConsensusTransactionKind::EndOfPublish(_)
351        | ConsensusTransactionKind::CapabilityNotificationV1(_)
352        | ConsensusTransactionKind::SignedCapabilityNotificationV1(_)
353        | ConsensusTransactionKind::RandomnessDkgMessage(_, _)
354        | ConsensusTransactionKind::RandomnessDkgConfirmation(_, _) => None,
355        ConsensusTransactionKind::UserTransactionV1(_)
356        | ConsensusTransactionKind::OverloadNotificationV1(_, _, _) => {
357            Some(config.enable_pcool_flow())
358        }
359        ConsensusTransactionKind::MisbehaviorReport(_) => Some(config.calculate_validator_scores()),
360        // zkLogin JWK support was never enabled; the variant is retained only
361        // for serialization compatibility.
362        ConsensusTransactionKind::NewJWKFetchedDeprecated => Some(false),
363        ConsensusTransactionKind::TransactionDenyRuleProposal(_) => {
364            Some(config.enable_pcool_flow())
365        }
366    }
367}
368
369/// A certified checkpoint summary with a placeholder signature, for seeding
370/// a test `CheckpointStore`. `end_of_epoch_data` makes it an epoch boundary.
371pub fn certified_summary(
372    epoch: EpochId,
373    sequence_number: u64,
374    end_of_epoch_data: Option<EndOfEpochData>,
375) -> CertifiedCheckpointSummary {
376    let summary = CheckpointSummary {
377        epoch,
378        sequence_number,
379        network_total_transactions: 0,
380        contents_digest: Default::default(),
381        previous_digest: None,
382        epoch_rolling_gas_cost_summary: GasCostSummary::default(),
383        end_of_epoch_data,
384        timestamp_ms: 0,
385        version_specific_data: Vec::new(),
386        checkpoint_commitments: Vec::new(),
387    };
388    let sig = AuthorityStrongQuorumSignInfo {
389        epoch,
390        signature: Default::default(),
391        signers_map: Default::default(),
392    };
393    Envelope::new_from_data_and_sig(summary, sig)
394}
395
396/// An executed (non-boundary) checkpoint for seeding a test
397/// `CheckpointStore`.
398pub fn executed_checkpoint(epoch: EpochId, sequence_number: u64) -> VerifiedCheckpoint {
399    VerifiedCheckpoint::new_unchecked(certified_summary(epoch, sequence_number, None))
400}