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::{collections::BTreeMap, path::PathBuf, sync::Arc, time::Duration};
6
7use fastcrypto::{hash::MultisetHash, traits::KeyPair};
8use futures::future::join_all;
9use iota_config::{genesis::Genesis, local_ip_utils, node::AuthorityOverloadConfig};
10use iota_framework::BuiltInFramework;
11use iota_genesis_builder::{
12    genesis_build_effects::GenesisBuildEffects, validator_info::ValidatorInfo,
13};
14use iota_move_build::{BuildConfig, CompiledPackage, IotaPackageHooks};
15use iota_protocol_config::ProtocolConfig;
16use iota_types::{
17    base_types::{
18        AuthorityName, ExecutionDigests, IotaAddress, ObjectID, ObjectRef, TransactionDigest,
19        random_object_ref,
20    },
21    committee::Committee,
22    crypto::{
23        AccountKeyPair, AuthorityKeyPair, AuthorityPublicKeyBytes, AuthoritySignInfo,
24        AuthoritySignature, IotaKeyPair, NetworkKeyPair, Signer, generate_proof_of_possession,
25        get_key_pair,
26    },
27    effects::{SignedTransactionEffects, TestEffectsBuilder},
28    error::IotaError,
29    message_envelope::Message,
30    object::Object,
31    signature_verification::VerifiedDigestCache,
32    transaction::{
33        CallArg, CertifiedTransaction, ObjectArg, SignedTransaction,
34        TEST_ONLY_GAS_UNIT_FOR_TRANSFER, Transaction, TransactionData,
35    },
36    utils::{create_fake_transaction, to_sender_signed_transaction},
37};
38use move_core_types::{account_address::AccountAddress, ident_str};
39use shared_crypto::intent::{Intent, IntentScope};
40use tokio::time::timeout;
41use tracing::{info, warn};
42
43use crate::{
44    authority::{AuthorityState, test_authority_builder::TestAuthorityBuilder},
45    authority_aggregator::{AuthorityAggregator, AuthorityAggregatorBuilder, TimeoutConfig},
46    state_accumulator::StateAccumulator,
47    test_authority_clients::LocalAuthorityClient,
48};
49
50const WAIT_FOR_TX_TIMEOUT: Duration = Duration::from_secs(15);
51
52pub async fn send_and_confirm_transaction(
53    authority: &AuthorityState,
54    fullnode: Option<&AuthorityState>,
55    transaction: Transaction,
56) -> Result<(CertifiedTransaction, SignedTransactionEffects), IotaError> {
57    // Make the initial request
58    let epoch_store = authority.load_epoch_store_one_call_per_task();
59    transaction.validity_check(epoch_store.protocol_config(), epoch_store.epoch())?;
60    let transaction = epoch_store.verify_transaction(transaction)?;
61    let response = authority
62        .handle_transaction(&epoch_store, transaction.clone())
63        .await?;
64    let vote = response.status.into_signed_for_testing();
65
66    // Collect signatures from a quorum of authorities
67    let committee = authority.clone_committee_for_testing();
68    let certificate =
69        CertifiedTransaction::new(transaction.into_message(), vec![vote.clone()], &committee)
70            .unwrap()
71            .try_into_verified_for_testing(&committee, &Default::default())
72            .unwrap();
73
74    // Submit the confirmation. *Now* execution actually happens, and it should fail
75    // when we try to look up our dummy module. we unfortunately don't get a
76    // very descriptive error message, but we can at least see that something went
77    // wrong inside the VM
78    //
79    // We also check the incremental effects of the transaction on the live object
80    // set against StateAccumulator for testing and regression detection
81    let state_acc = StateAccumulator::new_for_tests(authority.get_accumulator_store().clone());
82    let mut state = state_acc.accumulate_live_object_set();
83    let (result, _execution_error_opt) = authority.try_execute_for_test(&certificate).await?;
84    let state_after = state_acc.accumulate_live_object_set();
85    let effects_acc = state_acc.accumulate_effects(vec![result.inner().data().clone()]);
86    state.union(&effects_acc);
87
88    assert_eq!(state_after.digest(), state.digest());
89
90    if let Some(fullnode) = fullnode {
91        fullnode.try_execute_for_test(&certificate).await?;
92    }
93    Ok((certificate.into_inner(), result.into_inner()))
94}
95
96#[cfg(test)]
97pub(crate) fn init_state_parameters_from_rng<R>(rng: &mut R) -> (Genesis, AuthorityKeyPair)
98where
99    R: rand::CryptoRng + rand::RngCore,
100{
101    let dir = iota_macros::nondeterministic!(tempfile::TempDir::new().unwrap());
102    let network_config = iota_swarm_config::network_config_builder::ConfigBuilder::new(&dir)
103        .rng(rng)
104        .build();
105    let genesis = network_config.genesis;
106    let authority_key = network_config.validator_configs[0]
107        .authority_key_pair()
108        .copy();
109
110    (genesis, authority_key)
111}
112
113pub async fn wait_for_tx(digest: TransactionDigest, state: Arc<AuthorityState>) {
114    match timeout(
115        WAIT_FOR_TX_TIMEOUT,
116        state
117            .get_transaction_cache_reader()
118            .notify_read_executed_effects(&[digest]),
119    )
120    .await
121    {
122        Ok(_) => info!(?digest, "digest found"),
123        Err(e) => {
124            warn!(?digest, "digest not found!");
125            panic!("timed out waiting for effects of digest! {e}");
126        }
127    }
128}
129
130pub async fn wait_for_all_txes(digests: Vec<TransactionDigest>, state: Arc<AuthorityState>) {
131    match timeout(
132        WAIT_FOR_TX_TIMEOUT,
133        state
134            .get_transaction_cache_reader()
135            .notify_read_executed_effects(&digests),
136    )
137    .await
138    {
139        Ok(_) => info!(?digests, "all digests found"),
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 compile_basics_package() -> CompiledPackage {
181    compile_example_package("../../examples/move/basics")
182}
183
184pub fn compile_managed_coin_package() -> CompiledPackage {
185    compile_example_package("../../crates/iota-core/src/unit_tests/data/managed_coin")
186}
187
188pub fn compile_example_package(relative_path: &str) -> CompiledPackage {
189    move_package::package_hooks::register_package_hooks(Box::new(IotaPackageHooks));
190    let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
191    path.push(relative_path);
192
193    BuildConfig::new_for_testing().build(&path).unwrap()
194}
195
196async fn init_genesis(
197    committee_size: usize,
198    mut genesis_objects: Vec<Object>,
199) -> (
200    Genesis,
201    Vec<(AuthorityPublicKeyBytes, AuthorityKeyPair)>,
202    ObjectID,
203) {
204    // add object_basics package object to genesis
205    let modules: Vec<_> = compile_basics_package().get_modules().cloned().collect();
206    let genesis_move_packages: Vec<_> = BuiltInFramework::genesis_move_packages().collect();
207    let config = ProtocolConfig::get_for_max_version_UNSAFE();
208    let pkg = Object::new_package(
209        &modules,
210        TransactionDigest::genesis_marker(),
211        config.max_move_package_size(),
212        config.move_binary_format_version(),
213        &genesis_move_packages,
214    )
215    .unwrap();
216    let pkg_id = pkg.id();
217    genesis_objects.push(pkg);
218
219    let mut builder = iota_genesis_builder::Builder::new().add_objects(genesis_objects);
220    let mut key_pairs = Vec::new();
221    for i in 0..committee_size {
222        let authority_key_pair: AuthorityKeyPair = get_key_pair().1;
223        let authority_pubkey_bytes = authority_key_pair.public().into();
224        let protocol_key_pair: NetworkKeyPair = get_key_pair().1;
225        let protocol_pubkey = protocol_key_pair.public().clone();
226        let account_key_pair: IotaKeyPair = get_key_pair::<AccountKeyPair>().1.into();
227        let network_key_pair: NetworkKeyPair = get_key_pair().1;
228        let validator_info = ValidatorInfo {
229            name: format!("validator-{i}"),
230            authority_key: authority_pubkey_bytes,
231            protocol_key: protocol_pubkey,
232            account_address: IotaAddress::from(&account_key_pair.public()),
233            network_key: network_key_pair.public().clone(),
234            gas_price: 1,
235            commission_rate: 0,
236            network_address: local_ip_utils::new_local_tcp_address_for_testing(),
237            p2p_address: local_ip_utils::new_local_udp_address_for_testing(),
238            primary_address: local_ip_utils::new_local_udp_address_for_testing(),
239            description: String::new(),
240            image_url: String::new(),
241            project_url: String::new(),
242        };
243        let pop =
244            generate_proof_of_possession(&authority_key_pair, (&account_key_pair.public()).into());
245        builder = builder.add_validator(validator_info, pop);
246        key_pairs.push((authority_pubkey_bytes, authority_key_pair));
247    }
248    for (_, key) in &key_pairs {
249        builder = builder.add_validator_signature(key);
250    }
251
252    let GenesisBuildEffects { genesis, .. } = builder.build();
253    (genesis, key_pairs, pkg_id)
254}
255
256pub async fn init_local_authorities(
257    committee_size: usize,
258    genesis_objects: Vec<Object>,
259) -> (
260    AuthorityAggregator<LocalAuthorityClient>,
261    Vec<Arc<AuthorityState>>,
262    Genesis,
263    ObjectID,
264) {
265    let (genesis, key_pairs, framework) = init_genesis(committee_size, genesis_objects).await;
266    let authorities = join_all(key_pairs.iter().map(|(_, key_pair)| {
267        TestAuthorityBuilder::new()
268            .with_genesis_and_keypair(&genesis, key_pair)
269            .build()
270    }))
271    .await;
272    let aggregator = init_local_authorities_with_genesis(&genesis, authorities.clone()).await;
273    (aggregator, authorities, genesis, framework)
274}
275
276pub async fn init_local_authorities_with_overload_thresholds(
277    committee_size: usize,
278    genesis_objects: Vec<Object>,
279    overload_thresholds: AuthorityOverloadConfig,
280) -> (
281    AuthorityAggregator<LocalAuthorityClient>,
282    Vec<Arc<AuthorityState>>,
283    Genesis,
284    ObjectID,
285) {
286    let (genesis, key_pairs, framework) = init_genesis(committee_size, genesis_objects).await;
287    let authorities = join_all(key_pairs.iter().map(|(_, key_pair)| {
288        TestAuthorityBuilder::new()
289            .with_genesis_and_keypair(&genesis, key_pair)
290            .with_authority_overload_config(overload_thresholds.clone())
291            .build()
292    }))
293    .await;
294    let aggregator = init_local_authorities_with_genesis(&genesis, authorities.clone()).await;
295    (aggregator, authorities, genesis, framework)
296}
297
298pub async fn init_local_authorities_with_genesis(
299    genesis: &Genesis,
300    authorities: Vec<Arc<AuthorityState>>,
301) -> AuthorityAggregator<LocalAuthorityClient> {
302    telemetry_subscribers::init_for_testing();
303    let mut clients = BTreeMap::new();
304    for state in authorities {
305        let name = state.name;
306        let client = LocalAuthorityClient::new_from_authority(state);
307        clients.insert(name, client);
308    }
309    let timeouts = TimeoutConfig {
310        pre_quorum_timeout: Duration::from_secs(5),
311        post_quorum_timeout: Duration::from_secs(5),
312        serial_authority_request_interval: Duration::from_secs(1),
313    };
314    AuthorityAggregatorBuilder::from_genesis(genesis)
315        .with_timeouts_config(timeouts)
316        .build_custom_clients(clients)
317}
318
319pub fn make_transfer_iota_transaction(
320    gas_object: ObjectRef,
321    recipient: IotaAddress,
322    amount: Option<u64>,
323    sender: IotaAddress,
324    keypair: &AccountKeyPair,
325    gas_price: u64,
326) -> Transaction {
327    let data = TransactionData::new_transfer_iota(
328        recipient,
329        sender,
330        amount,
331        gas_object,
332        gas_price * TEST_ONLY_GAS_UNIT_FOR_TRANSFER,
333        gas_price,
334    );
335    to_sender_signed_transaction(data, keypair)
336}
337
338pub fn make_pay_iota_transaction(
339    gas_object: ObjectRef,
340    coins: Vec<ObjectRef>,
341    recipients: Vec<IotaAddress>,
342    amounts: Vec<u64>,
343    sender: IotaAddress,
344    keypair: &AccountKeyPair,
345    gas_price: u64,
346    gas_budget: u64,
347) -> Transaction {
348    let data = TransactionData::new_pay_iota(
349        sender, coins, recipients, amounts, gas_object, gas_budget, gas_price,
350    )
351    .unwrap();
352    to_sender_signed_transaction(data, keypair)
353}
354
355pub fn make_transfer_object_transaction(
356    object_ref: ObjectRef,
357    gas_object: ObjectRef,
358    sender: IotaAddress,
359    keypair: &AccountKeyPair,
360    recipient: IotaAddress,
361    gas_price: u64,
362) -> Transaction {
363    let data = TransactionData::new_transfer(
364        recipient,
365        object_ref,
366        sender,
367        gas_object,
368        gas_price * TEST_ONLY_GAS_UNIT_FOR_TRANSFER * 10,
369        gas_price,
370    );
371    to_sender_signed_transaction(data, keypair)
372}
373
374pub fn make_transfer_object_move_transaction(
375    src: IotaAddress,
376    keypair: &AccountKeyPair,
377    dest: IotaAddress,
378    object_ref: ObjectRef,
379    framework_obj_id: ObjectID,
380    gas_object_ref: ObjectRef,
381    gas_budget_in_units: u64,
382    gas_price: u64,
383) -> Transaction {
384    let args = vec![
385        CallArg::Object(ObjectArg::ImmOrOwnedObject(object_ref)),
386        CallArg::Pure(bcs::to_bytes(&AccountAddress::from(dest)).unwrap()),
387    ];
388
389    to_sender_signed_transaction(
390        TransactionData::new_move_call(
391            src,
392            framework_obj_id,
393            ident_str!("object_basics").to_owned(),
394            ident_str!("transfer").to_owned(),
395            Vec::new(),
396            gas_object_ref,
397            args,
398            gas_budget_in_units * gas_price,
399            gas_price,
400        )
401        .unwrap(),
402        keypair,
403    )
404}
405
406/// Make a dummy tx that uses random object refs.
407pub fn make_dummy_tx(
408    receiver: IotaAddress,
409    sender: IotaAddress,
410    sender_sec: &AccountKeyPair,
411) -> Transaction {
412    Transaction::from_data_and_signer(
413        TransactionData::new_transfer(
414            receiver,
415            random_object_ref(),
416            sender,
417            random_object_ref(),
418            TEST_ONLY_GAS_UNIT_FOR_TRANSFER * 10,
419            10,
420        ),
421        vec![sender_sec],
422    )
423}
424
425/// Make a cert using an arbitrarily large committee.
426pub fn make_cert_with_large_committee(
427    committee: &Committee,
428    key_pairs: &[AuthorityKeyPair],
429    transaction: &Transaction,
430) -> CertifiedTransaction {
431    // assumes equal weighting.
432    let len = committee.voting_rights.len();
433    assert_eq!(len, key_pairs.len());
434    let count = (len * 2).div_ceil(3);
435
436    let sigs: Vec<_> = key_pairs
437        .iter()
438        .take(count)
439        .map(|key_pair| {
440            SignedTransaction::new(
441                committee.epoch(),
442                transaction.clone().into_data(),
443                key_pair,
444                AuthorityPublicKeyBytes::from(key_pair.public()),
445            )
446            .auth_sig()
447            .clone()
448        })
449        .collect();
450
451    let cert = CertifiedTransaction::new(transaction.clone().into_data(), sigs, committee).unwrap();
452    cert.verify_signatures_authenticated(
453        committee,
454        &Default::default(),
455        Arc::new(VerifiedDigestCache::new_empty()),
456    )
457    .unwrap();
458    cert
459}