iota_core/authority/
authority_test_utils.rs

1// Copyright (c) 2021, Facebook, Inc. and its affiliates
2// Copyright (c) Mysten Labs, Inc.
3// Modifications Copyright (c) 2024 IOTA Stiftung
4// SPDX-License-Identifier: Apache-2.0
5
6use core::default::Default;
7
8use fastcrypto::{hash::MultisetHash, traits::KeyPair};
9use iota_move_build::{BuildConfig, CompiledPackage};
10use iota_types::{
11    crypto::{AccountKeyPair, AuthorityKeyPair, Signature},
12    messages_consensus::ConsensusTransaction,
13    move_package::UpgradePolicy,
14    programmable_transaction_builder::ProgrammableTransactionBuilder,
15    utils::to_sender_signed_transaction,
16};
17use move_core_types::account_address::AccountAddress;
18use move_symbol_pool::Symbol;
19
20use super::{test_authority_builder::TestAuthorityBuilder, *};
21use crate::{checkpoints::CheckpointServiceNoop, consensus_handler::SequencedConsensusTransaction};
22
23pub async fn send_and_confirm_transaction(
24    authority: &AuthorityState,
25    transaction: Transaction,
26) -> Result<(CertifiedTransaction, SignedTransactionEffects), IotaError> {
27    send_and_confirm_transaction_(
28        authority,
29        None, // no fullnode_key_pair
30        transaction,
31        false, // no shared objects
32    )
33    .await
34}
35pub async fn send_and_confirm_transaction_(
36    authority: &AuthorityState,
37    fullnode: Option<&AuthorityState>,
38    transaction: Transaction,
39    with_shared: bool, // transaction includes shared objects
40) -> Result<(CertifiedTransaction, SignedTransactionEffects), IotaError> {
41    let (txn, effects, _execution_error_opt) = send_and_confirm_transaction_with_execution_error(
42        authority,
43        fullnode,
44        transaction,
45        with_shared,
46        true,
47    )
48    .await?;
49    Ok((txn, effects))
50}
51
52pub async fn certify_transaction(
53    authority: &AuthorityState,
54    transaction: Transaction,
55) -> Result<VerifiedCertificate, IotaError> {
56    // Make the initial request
57    let epoch_store = authority.load_epoch_store_one_call_per_task();
58    // TODO: Move this check to a more appropriate place.
59    transaction.validity_check(epoch_store.protocol_config(), epoch_store.epoch())?;
60    let transaction = epoch_store.verify_transaction(transaction).unwrap();
61
62    let response = authority
63        .handle_transaction(&epoch_store, transaction.clone())
64        .await?;
65    let vote = response.status.into_signed_for_testing();
66
67    // Collect signatures from a quorum of authorities
68    let committee = authority.clone_committee_for_testing();
69    let certificate = CertifiedTransaction::new(transaction.into_message(), vec![vote], &committee)
70        .unwrap()
71        .try_into_verified_for_testing(&committee, &Default::default())
72        .unwrap();
73    Ok(certificate)
74}
75
76pub async fn execute_certificate_with_execution_error(
77    authority: &AuthorityState,
78    fullnode: Option<&AuthorityState>,
79    certificate: VerifiedCertificate,
80    with_shared: bool, // transaction includes shared objects
81    fake_consensus: bool,
82) -> Result<
83    (
84        CertifiedTransaction,
85        SignedTransactionEffects,
86        Option<ExecutionError>,
87    ),
88    IotaError,
89> {
90    // We also check the incremental effects of the transaction on the live object
91    // set against StateAccumulator for testing and regression detection.
92    // We must do this before sending to consensus, otherwise consensus may already
93    // lead to transaction execution and state change.
94    let state_acc = StateAccumulator::new_for_tests(authority.get_accumulator_store().clone());
95    let mut state = state_acc.accumulate_cached_live_object_set_for_testing();
96
97    if with_shared {
98        if fake_consensus {
99            send_consensus(authority, &certificate).await;
100        } else {
101            // Just set object locks directly if send_consensus is not requested.
102            authority
103                .epoch_store_for_testing()
104                .assign_shared_object_versions_for_tests(
105                    authority.get_object_cache_reader().as_ref(),
106                    &vec![VerifiedExecutableTransaction::new_from_certificate(
107                        certificate.clone(),
108                    )],
109                )
110                .await?;
111        }
112        if let Some(fullnode) = fullnode {
113            fullnode
114                .epoch_store_for_testing()
115                .assign_shared_object_versions_for_tests(
116                    fullnode.get_object_cache_reader().as_ref(),
117                    &vec![VerifiedExecutableTransaction::new_from_certificate(
118                        certificate.clone(),
119                    )],
120                )
121                .await?;
122        }
123    }
124
125    // Submit the confirmation. *Now* execution actually happens, and it should fail
126    // when we try to look up our dummy module. we unfortunately don't get a
127    // very descriptive error message, but we can at least see that something went
128    // wrong inside the VM
129    let (result, execution_error_opt) = authority.try_execute_for_test(&certificate).await?;
130    let state_after = state_acc.accumulate_cached_live_object_set_for_testing();
131    let effects_acc = state_acc.accumulate_effects(vec![result.inner().data().clone()]);
132    state.union(&effects_acc);
133
134    assert_eq!(state_after.digest(), state.digest());
135
136    if let Some(fullnode) = fullnode {
137        fullnode.try_execute_for_test(&certificate).await?;
138    }
139    Ok((
140        certificate.into_inner(),
141        result.into_inner(),
142        execution_error_opt,
143    ))
144}
145
146pub async fn send_and_confirm_transaction_with_execution_error(
147    authority: &AuthorityState,
148    fullnode: Option<&AuthorityState>,
149    transaction: Transaction,
150    with_shared: bool,    // transaction includes shared objects
151    fake_consensus: bool, // runs consensus handler if true
152) -> Result<
153    (
154        CertifiedTransaction,
155        SignedTransactionEffects,
156        Option<ExecutionError>,
157    ),
158    IotaError,
159> {
160    let certificate = certify_transaction(authority, transaction).await?;
161    execute_certificate_with_execution_error(
162        authority,
163        fullnode,
164        certificate,
165        with_shared,
166        fake_consensus,
167    )
168    .await
169}
170
171pub async fn init_state_validator_with_fullnode() -> (Arc<AuthorityState>, Arc<AuthorityState>) {
172    use iota_types::crypto::get_authority_key_pair;
173
174    let validator = TestAuthorityBuilder::new().build().await;
175    let fullnode_key_pair = get_authority_key_pair().1;
176    let fullnode = TestAuthorityBuilder::new()
177        .with_keypair(&fullnode_key_pair)
178        .build()
179        .await;
180    (validator, fullnode)
181}
182
183pub async fn init_state_with_committee(
184    genesis: &Genesis,
185    authority_key: &AuthorityKeyPair,
186) -> Arc<AuthorityState> {
187    TestAuthorityBuilder::new()
188        .with_genesis_and_keypair(genesis, authority_key)
189        .build()
190        .await
191}
192
193pub async fn init_state_with_ids<I: IntoIterator<Item = (IotaAddress, ObjectID)>>(
194    objects: I,
195) -> Arc<AuthorityState> {
196    let state = TestAuthorityBuilder::new().build().await;
197    for (address, object_id) in objects {
198        let obj = Object::with_id_owner_for_testing(object_id, address);
199        // TODO: Make this part of genesis initialization instead of explicit insert.
200        state.insert_genesis_object(obj).await;
201    }
202    state
203}
204
205pub async fn init_state_with_ids_and_versions<
206    I: IntoIterator<Item = (IotaAddress, ObjectID, SequenceNumber)>,
207>(
208    objects: I,
209) -> Arc<AuthorityState> {
210    let state = TestAuthorityBuilder::new().build().await;
211    for (address, object_id, version) in objects {
212        let obj = Object::with_id_owner_version_for_testing(object_id, version, address);
213        state.insert_genesis_object(obj).await;
214    }
215    state
216}
217
218pub async fn init_state_with_objects<I: IntoIterator<Item = Object>>(
219    objects: I,
220) -> Arc<AuthorityState> {
221    let dir = tempfile::TempDir::new().unwrap();
222    let network_config =
223        iota_swarm_config::network_config_builder::ConfigBuilder::new(&dir).build();
224    let genesis = network_config.genesis;
225    let keypair = network_config.validator_configs[0]
226        .authority_key_pair()
227        .copy();
228    init_state_with_objects_and_committee(objects, &genesis, &keypair).await
229}
230
231pub async fn init_state_with_objects_and_committee<I: IntoIterator<Item = Object>>(
232    objects: I,
233    genesis: &Genesis,
234    authority_key: &AuthorityKeyPair,
235) -> Arc<AuthorityState> {
236    let state = init_state_with_committee(genesis, authority_key).await;
237    for o in objects {
238        state.insert_genesis_object(o).await;
239    }
240    state
241}
242
243pub async fn init_state_with_object_id(
244    address: IotaAddress,
245    object: ObjectID,
246) -> Arc<AuthorityState> {
247    init_state_with_ids(std::iter::once((address, object))).await
248}
249
250pub async fn init_state_with_ids_and_expensive_checks<
251    I: IntoIterator<Item = (IotaAddress, ObjectID)>,
252>(
253    objects: I,
254    config: ExpensiveSafetyCheckConfig,
255) -> Arc<AuthorityState> {
256    let state = TestAuthorityBuilder::new()
257        .with_expensive_safety_checks(config)
258        .build()
259        .await;
260    for (address, object_id) in objects {
261        let obj = Object::with_id_owner_for_testing(object_id, address);
262        // TODO: Make this part of genesis initialization instead of explicit insert.
263        state.insert_genesis_object(obj).await;
264    }
265    state
266}
267
268pub fn init_transfer_transaction(
269    authority_state: &AuthorityState,
270    sender: IotaAddress,
271    secret: &AccountKeyPair,
272    recipient: IotaAddress,
273    object_ref: ObjectRef,
274    gas_object_ref: ObjectRef,
275    gas_budget: u64,
276    gas_price: u64,
277) -> VerifiedTransaction {
278    let data = TransactionData::new_transfer(
279        recipient,
280        object_ref,
281        sender,
282        gas_object_ref,
283        gas_budget,
284        gas_price,
285    );
286    let tx = to_sender_signed_transaction(data, secret);
287    authority_state
288        .epoch_store_for_testing()
289        .verify_transaction(tx)
290        .unwrap()
291}
292
293pub fn init_certified_transfer_transaction(
294    sender: IotaAddress,
295    secret: &AccountKeyPair,
296    recipient: IotaAddress,
297    object_ref: ObjectRef,
298    gas_object_ref: ObjectRef,
299    authority_state: &AuthorityState,
300) -> VerifiedCertificate {
301    let rgp = authority_state.reference_gas_price_for_testing().unwrap();
302    let transfer_transaction = init_transfer_transaction(
303        authority_state,
304        sender,
305        secret,
306        recipient,
307        object_ref,
308        gas_object_ref,
309        rgp * TEST_ONLY_GAS_UNIT_FOR_TRANSFER,
310        rgp,
311    );
312    init_certified_transaction(transfer_transaction.into(), authority_state)
313}
314
315pub fn init_certified_transaction(
316    transaction: Transaction,
317    authority_state: &AuthorityState,
318) -> VerifiedCertificate {
319    let epoch_store = authority_state.epoch_store_for_testing();
320    let transaction = epoch_store.verify_transaction(transaction).unwrap();
321
322    let vote = VerifiedSignedTransaction::new(
323        0,
324        transaction.clone(),
325        authority_state.name,
326        &*authority_state.secret,
327    );
328    CertifiedTransaction::new(
329        transaction.into_message(),
330        vec![vote.auth_sig().clone()],
331        epoch_store.committee(),
332    )
333    .unwrap()
334    .try_into_verified_for_testing(epoch_store.committee(), &Default::default())
335    .unwrap()
336}
337
338pub async fn certify_shared_obj_transaction_no_execution(
339    authority: &AuthorityState,
340    transaction: Transaction,
341) -> Result<VerifiedCertificate, IotaError> {
342    let epoch_store = authority.load_epoch_store_one_call_per_task();
343    let transaction = epoch_store.verify_transaction(transaction).unwrap();
344    let response = authority
345        .handle_transaction(&epoch_store, transaction.clone())
346        .await?;
347    let vote = response.status.into_signed_for_testing();
348
349    // Collect signatures from a quorum of authorities
350    let committee = authority.clone_committee_for_testing();
351    let certificate =
352        CertifiedTransaction::new(transaction.into_message(), vec![vote.clone()], &committee)
353            .unwrap()
354            .try_into_verified_for_testing(&committee, &Default::default())
355            .unwrap();
356
357    send_consensus_no_execution(authority, &certificate).await;
358
359    Ok(certificate)
360}
361
362pub async fn enqueue_all_and_execute_all(
363    authority: &AuthorityState,
364    certificates: Vec<VerifiedCertificate>,
365) -> Result<Vec<TransactionEffects>, IotaError> {
366    authority.enqueue_certificates_for_execution(
367        certificates.clone(),
368        &authority.epoch_store_for_testing(),
369    );
370    let mut output = Vec::new();
371    for cert in certificates {
372        let effects = authority.notify_read_effects(&cert).await?;
373        output.push(effects);
374    }
375    Ok(output)
376}
377
378pub async fn execute_sequenced_certificate_to_effects(
379    authority: &AuthorityState,
380    certificate: VerifiedCertificate,
381) -> Result<(TransactionEffects, Option<ExecutionError>), IotaError> {
382    authority.enqueue_certificates_for_execution(
383        vec![certificate.clone()],
384        &authority.epoch_store_for_testing(),
385    );
386
387    let (result, execution_error_opt) = authority.try_execute_for_test(&certificate).await?;
388    let effects = result.inner().data().clone();
389    Ok((effects, execution_error_opt))
390}
391
392pub async fn send_consensus(authority: &AuthorityState, cert: &VerifiedCertificate) {
393    let transaction = SequencedConsensusTransaction::new_test(
394        ConsensusTransaction::new_certificate_message(&authority.name, cert.clone().into_inner()),
395    );
396
397    let certs = authority
398        .epoch_store_for_testing()
399        .process_consensus_transactions_for_tests(
400            vec![transaction],
401            &Arc::new(CheckpointServiceNoop {}),
402            authority.get_object_cache_reader().as_ref(),
403            &authority.metrics,
404            true,
405        )
406        .await
407        .unwrap();
408
409    authority
410        .transaction_manager()
411        .enqueue(certs, &authority.epoch_store_for_testing());
412}
413
414pub async fn send_consensus_no_execution(authority: &AuthorityState, cert: &VerifiedCertificate) {
415    let transaction = SequencedConsensusTransaction::new_test(
416        ConsensusTransaction::new_certificate_message(&authority.name, cert.clone().into_inner()),
417    );
418
419    // Call process_consensus_transaction() instead of
420    // handle_consensus_transaction(), to avoid actually executing cert.
421    // This allows testing cert execution independently.
422    authority
423        .epoch_store_for_testing()
424        .process_consensus_transactions_for_tests(
425            vec![transaction],
426            &Arc::new(CheckpointServiceNoop {}),
427            authority.get_object_cache_reader().as_ref(),
428            &authority.metrics,
429            true,
430        )
431        .await
432        .unwrap();
433}
434
435pub async fn send_batch_consensus_no_execution(
436    authority: &AuthorityState,
437    certificates: &[VerifiedCertificate],
438    skip_consensus_commit_prologue_in_test: bool,
439) -> Vec<VerifiedExecutableTransaction> {
440    let transactions = certificates
441        .iter()
442        .map(|cert| {
443            SequencedConsensusTransaction::new_test(ConsensusTransaction::new_certificate_message(
444                &authority.name,
445                cert.clone().into_inner(),
446            ))
447        })
448        .collect();
449
450    // Call process_consensus_transaction() instead of
451    // handle_consensus_transaction(), to avoid actually executing cert.
452    // This allows testing cert execution independently.
453    authority
454        .epoch_store_for_testing()
455        .process_consensus_transactions_for_tests(
456            transactions,
457            &Arc::new(CheckpointServiceNoop {}),
458            authority.get_object_cache_reader().as_ref(),
459            &authority.metrics,
460            skip_consensus_commit_prologue_in_test,
461        )
462        .await
463        .unwrap()
464}
465
466pub fn build_test_modules_with_dep_addr(
467    path: &Path,
468    dep_original_addresses: impl IntoIterator<Item = (&'static str, ObjectID)>,
469    dep_ids: impl IntoIterator<Item = (&'static str, ObjectID)>,
470) -> CompiledPackage {
471    let mut build_config = BuildConfig::new_for_testing();
472    for (addr_name, obj_id) in dep_original_addresses {
473        build_config
474            .config
475            .additional_named_addresses
476            .insert(addr_name.to_string(), AccountAddress::from(obj_id));
477    }
478    let mut package = build_config.build(path).unwrap();
479
480    let dep_id_mapping: BTreeMap<_, _> = dep_ids
481        .into_iter()
482        .map(|(dep_name, obj_id)| (Symbol::from(dep_name), obj_id))
483        .collect();
484
485    assert_eq!(
486        dep_id_mapping.len(),
487        package.dependency_ids.unpublished.len()
488    );
489    for unpublished_dep in &package.dependency_ids.unpublished {
490        let published_id = dep_id_mapping.get(unpublished_dep).unwrap();
491        // Make sure we aren't overriding a package
492        assert!(
493            package
494                .dependency_ids
495                .published
496                .insert(*unpublished_dep, *published_id)
497                .is_none()
498        )
499    }
500
501    // No unpublished deps
502    package.dependency_ids.unpublished.clear();
503    package
504}
505
506/// Returns the new package's ID and the upgrade cap object ref.
507/// `dep_original_addresses` allows us to fill out mappings in the addresses
508/// section of the package manifest. These IDs must be the original IDs of
509/// names. dep_ids are the IDs of the dependencies of the package, in the latest
510/// version (if there were upgrades).
511pub async fn publish_package_on_single_authority(
512    path: &Path,
513    sender: IotaAddress,
514    sender_key: &dyn Signer<Signature>,
515    gas_payment: ObjectRef,
516    dep_original_addresses: impl IntoIterator<Item = (&'static str, ObjectID)>,
517    dep_ids: Vec<ObjectID>,
518    state: &Arc<AuthorityState>,
519) -> IotaResult<(ObjectID, ObjectRef)> {
520    let mut build_config = BuildConfig::new_for_testing();
521    for (addr_name, obj_id) in dep_original_addresses {
522        build_config
523            .config
524            .additional_named_addresses
525            .insert(addr_name.to_string(), AccountAddress::from(obj_id));
526    }
527    let modules = build_config.build(path).unwrap().get_package_bytes(false);
528
529    let mut builder = ProgrammableTransactionBuilder::new();
530    let cap = builder.publish_upgradeable(modules, dep_ids);
531    builder.transfer_arg(sender, cap);
532    let pt = builder.finish();
533
534    let rgp = state.epoch_store_for_testing().reference_gas_price();
535    let txn_data = TransactionData::new_programmable(
536        sender,
537        vec![gas_payment],
538        pt,
539        rgp * TEST_ONLY_GAS_UNIT_FOR_PUBLISH,
540        rgp,
541    );
542
543    let signed = to_sender_signed_transaction(txn_data, sender_key);
544    let (_cert, effects) = send_and_confirm_transaction(state, signed).await?;
545    assert!(effects.data().status().is_ok());
546    let package_id = effects
547        .data()
548        .created()
549        .iter()
550        .find(|c| c.1 == Owner::Immutable)
551        .unwrap()
552        .0
553        .0;
554    let cap_object = effects
555        .data()
556        .created()
557        .iter()
558        .find(|c| matches!(c.1, Owner::AddressOwner(..)))
559        .unwrap()
560        .0;
561    Ok((package_id, cap_object))
562}
563
564pub async fn upgrade_package_on_single_authority(
565    path: &Path,
566    sender: IotaAddress,
567    sender_key: &dyn Signer<Signature>,
568    gas_payment: ObjectRef,
569    package_id: ObjectID,
570    upgrade_cap: ObjectRef,
571    dep_original_addresses: impl IntoIterator<Item = (&'static str, ObjectID)>,
572    dep_id_mapping: impl IntoIterator<Item = (&'static str, ObjectID)>,
573    state: &Arc<AuthorityState>,
574) -> IotaResult<ObjectID> {
575    let package = build_test_modules_with_dep_addr(path, dep_original_addresses, dep_id_mapping);
576
577    let with_unpublished_deps = false;
578    let modules = package.get_package_bytes(with_unpublished_deps);
579    let digest = package.get_package_digest(with_unpublished_deps).to_vec();
580
581    let rgp = state.epoch_store_for_testing().reference_gas_price();
582    let data = TransactionData::new_upgrade(
583        sender,
584        gas_payment,
585        package_id,
586        modules,
587        package.get_dependency_storage_package_ids(),
588        (upgrade_cap, Owner::AddressOwner(sender)),
589        UpgradePolicy::COMPATIBLE,
590        digest,
591        rgp * TEST_ONLY_GAS_UNIT_FOR_PUBLISH,
592        rgp,
593    )
594    .unwrap();
595    let signed = to_sender_signed_transaction(data, sender_key);
596    let (_cert, effects) = send_and_confirm_transaction(state, signed).await?;
597    assert!(effects.data().status().is_ok());
598    let package_id = effects
599        .data()
600        .created()
601        .iter()
602        .find(|c| c.1 == Owner::Immutable)
603        .unwrap()
604        .0
605        .0;
606    Ok(package_id)
607}