Skip to main content

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 std::sync::Arc;
7
8use fastcrypto::{hash::MultisetHash, traits::KeyPair};
9use iota_config::{genesis::Genesis, node::ExpensiveSafetyCheckConfig};
10use iota_sdk_types::{
11    Address, ObjectId, ObjectReference, Owner, RandomnessRound, Transaction, TransactionEffects,
12    Version, VersionAssignment,
13};
14use iota_types::{
15    crypto::{AccountPrivateKey, AuthorityKeyPair},
16    effects::SignedTransactionEffects,
17    error::{ExecutionError, IotaError},
18    executable_transaction::VerifiedExecutableTransaction,
19    messages_consensus::ConsensusTransaction,
20    object::Object,
21    signature::VerifyParams,
22    transaction::{
23        CertifiedTransaction, SenderSignedTransactionAPI, TEST_ONLY_GAS_UNIT_FOR_TRANSFER,
24        TransactionAPI, TransactionEnvelope, TransactionKey, VerifiedCertificate,
25        VerifiedSignedTransaction, VerifiedTransaction,
26    },
27    utils::to_sender_signed_transaction,
28};
29
30use super::{
31    authority_per_epoch_store::AuthorityPerEpochStore,
32    epoch_start_configuration::EpochStartConfigTrait,
33    shared_object_version_manager::{AssignedTxAndVersions, AssignedVersions, Schedulable},
34    test_authority_builder::TestAuthorityBuilder,
35};
36use crate::{
37    authority::{AuthorityState, ExecutionEnv},
38    checkpoints::CheckpointServiceNoop,
39    consensus_handler::SequencedConsensusTransaction,
40    execution_scheduler::{
41        ExecutionSchedulerAPI, transaction_manager::SchedulableAttestedTransaction,
42    },
43    global_state_hasher::GlobalStateHasher,
44};
45
46pub async fn send_and_confirm_transaction(
47    authority: &AuthorityState,
48    transaction: TransactionEnvelope,
49) -> Result<(CertifiedTransaction, SignedTransactionEffects), IotaError> {
50    send_and_confirm_transaction_(
51        authority,
52        None, // no fullnode_key_pair
53        transaction,
54        false, // no shared objects
55    )
56    .await
57}
58pub async fn send_and_confirm_transaction_(
59    authority: &AuthorityState,
60    fullnode: Option<&AuthorityState>,
61    transaction: TransactionEnvelope,
62    with_shared: bool, // transaction includes shared objects
63) -> Result<(CertifiedTransaction, SignedTransactionEffects), IotaError> {
64    let (txn, effects, _execution_error_opt) = send_and_confirm_transaction_with_execution_error(
65        authority,
66        fullnode,
67        transaction,
68        with_shared,
69        true,
70    )
71    .await?;
72    Ok((txn, effects))
73}
74
75pub async fn certify_transaction(
76    authority: &AuthorityState,
77    transaction: TransactionEnvelope,
78) -> Result<VerifiedCertificate, IotaError> {
79    // Make the initial request
80    let epoch_store = authority.load_epoch_store_one_call_per_task();
81    // TODO: Move this check to a more appropriate place.
82    transaction.validity_check(&epoch_store.tx_validity_check_context())?;
83    let transaction = epoch_store.verify_transaction(transaction).unwrap();
84
85    let response = authority
86        .handle_transaction(&epoch_store, transaction.clone())
87        .await?;
88    let vote = response.status.into_signed_for_testing();
89    // As in `init_certified_transaction`.
90    epoch_store.assign_to_a_new_commit_for_testing(&[transaction.key()]);
91
92    // Collect signatures from a quorum of authorities
93    let committee = authority.clone_committee_for_testing();
94    let certificate = CertifiedTransaction::new(transaction.into_message(), vec![vote], &committee)
95        .unwrap()
96        .try_into_verified_for_testing(
97            &committee,
98            &VerifyParams::from_protocol_config(epoch_store.protocol_config()),
99        )
100        .unwrap();
101    Ok(certificate)
102}
103
104pub async fn execute_certificate_with_execution_error(
105    authority: &AuthorityState,
106    fullnode: Option<&AuthorityState>,
107    certificate: VerifiedCertificate,
108    with_shared: bool, // transaction includes shared objects
109    fake_consensus: bool,
110) -> Result<
111    (
112        CertifiedTransaction,
113        SignedTransactionEffects,
114        Option<ExecutionError>,
115    ),
116    IotaError,
117> {
118    // We also check the incremental effects of the transaction on the live object
119    // set against GlobalStateHasher for testing and regression detection.
120    // We must do this before sending to consensus, otherwise consensus may already
121    // lead to transaction execution and state change.
122    let state_acc =
123        GlobalStateHasher::new_for_tests(authority.get_global_state_hash_store().clone());
124    let mut state = state_acc.accumulate_cached_live_object_set_for_testing()?;
125
126    let assigned_versions = if with_shared {
127        if fake_consensus {
128            send_consensus(authority, &certificate).await
129        } else {
130            // Just set object locks directly if send_consensus is not requested.
131            let assigned_versions = authority
132                .epoch_store_for_testing()
133                .assign_shared_object_versions_for_tests(
134                    authority.get_object_cache_reader().as_ref(),
135                    &[VerifiedExecutableTransaction::new_from_certificate(
136                        certificate.clone(),
137                    )],
138                )?;
139            assigned_versions
140                .into_map()
141                .get(&certificate.key())
142                .cloned()
143                .unwrap()
144        }
145    } else {
146        vec![]
147    };
148
149    // Submit the confirmation. *Now* execution actually happens, and it should fail
150    // when we try to look up our dummy module. we unfortunately don't get a
151    // very descriptive error message, but we can at least see that something went
152    // wrong inside the VM
153    let (result, execution_error_opt) = authority.try_execute_for_test(
154        &certificate,
155        ExecutionEnv::new().with_assigned_versions(assigned_versions.clone()),
156    )?;
157    let state_after = state_acc.accumulate_cached_live_object_set_for_testing()?;
158    let effects_acc = state_acc.accumulate_effects(&[result.inner().data().clone()]);
159    state.union(&effects_acc);
160
161    assert_eq!(state_after.digest(), state.digest());
162
163    if let Some(fullnode) = fullnode {
164        fullnode.try_execute_for_test(
165            &certificate,
166            ExecutionEnv::new().with_assigned_versions(assigned_versions),
167        )?;
168    }
169    Ok((
170        certificate.into_inner(),
171        result.into_inner(),
172        execution_error_opt,
173    ))
174}
175
176pub async fn send_and_confirm_transaction_with_execution_error(
177    authority: &AuthorityState,
178    fullnode: Option<&AuthorityState>,
179    transaction: TransactionEnvelope,
180    with_shared: bool,    // transaction includes shared objects
181    fake_consensus: bool, // runs consensus handler if true
182) -> Result<
183    (
184        CertifiedTransaction,
185        SignedTransactionEffects,
186        Option<ExecutionError>,
187    ),
188    IotaError,
189> {
190    let certificate = certify_transaction(authority, transaction).await?;
191    execute_certificate_with_execution_error(
192        authority,
193        fullnode,
194        certificate,
195        with_shared,
196        fake_consensus,
197    )
198    .await
199}
200
201pub async fn init_state_validator_with_fullnode() -> (Arc<AuthorityState>, Arc<AuthorityState>) {
202    use iota_types::crypto::get_authority_key_pair;
203
204    let validator = TestAuthorityBuilder::new().build().await;
205    let fullnode_key_pair = get_authority_key_pair().1;
206    let fullnode = TestAuthorityBuilder::new()
207        .with_keypair(&fullnode_key_pair)
208        .build()
209        .await;
210    (validator, fullnode)
211}
212
213pub async fn init_state_with_committee(
214    genesis: &Genesis,
215    authority_key: &AuthorityKeyPair,
216) -> Arc<AuthorityState> {
217    TestAuthorityBuilder::new()
218        .with_genesis_and_keypair(genesis, authority_key)
219        .build()
220        .await
221}
222
223pub async fn init_state_with_ids<I: IntoIterator<Item = (Address, ObjectId)>>(
224    objects: I,
225) -> Arc<AuthorityState> {
226    let state = TestAuthorityBuilder::new().build().await;
227    for (address, object_id) in objects {
228        let obj = Object::with_id_owner_for_testing(object_id, address);
229        // TODO: Make this part of genesis initialization instead of explicit insert.
230        state.insert_genesis_object(obj);
231    }
232    state
233}
234
235pub async fn init_state_with_ids_and_versions<
236    I: IntoIterator<Item = (Address, ObjectId, Version)>,
237>(
238    objects: I,
239) -> Arc<AuthorityState> {
240    let state = TestAuthorityBuilder::new().build().await;
241    for (address, object_id, version) in objects {
242        let obj =
243            Object::with_id_owner_version_for_testing(object_id, version, Owner::Address(address));
244        state.insert_genesis_object(obj);
245    }
246    state
247}
248
249pub async fn init_state_with_objects<I: IntoIterator<Item = Object>>(
250    objects: I,
251) -> Arc<AuthorityState> {
252    let dir = tempfile::TempDir::new().unwrap();
253    let network_config =
254        iota_swarm_config::network_config_builder::ConfigBuilder::new(&dir).build();
255    let genesis = network_config.genesis;
256    let keypair = network_config.validator_configs[0]
257        .authority_key_pair()
258        .copy();
259    init_state_with_objects_and_committee(objects, &genesis, &keypair).await
260}
261
262pub async fn init_state_with_objects_and_committee<I: IntoIterator<Item = Object>>(
263    objects: I,
264    genesis: &Genesis,
265    authority_key: &AuthorityKeyPair,
266) -> Arc<AuthorityState> {
267    let state = init_state_with_committee(genesis, authority_key).await;
268    for o in objects {
269        state.insert_genesis_object(o);
270    }
271    state
272}
273
274pub async fn init_state_with_object_id(address: Address, object: ObjectId) -> Arc<AuthorityState> {
275    init_state_with_ids(std::iter::once((address, object))).await
276}
277
278pub async fn init_state_with_ids_and_expensive_checks<
279    I: IntoIterator<Item = (Address, ObjectId)>,
280>(
281    objects: I,
282    config: ExpensiveSafetyCheckConfig,
283) -> Arc<AuthorityState> {
284    let state = TestAuthorityBuilder::new()
285        .with_expensive_safety_checks(config)
286        .build()
287        .await;
288    for (address, object_id) in objects {
289        let obj = Object::with_id_owner_for_testing(object_id, address);
290        // TODO: Make this part of genesis initialization instead of explicit insert.
291        state.insert_genesis_object(obj);
292    }
293    state
294}
295
296pub fn init_transfer_transaction(
297    authority_state: &AuthorityState,
298    sender: Address,
299    secret: &AccountPrivateKey,
300    recipient: Address,
301    object_ref: ObjectReference,
302    gas_object_ref: ObjectReference,
303    gas_budget: u64,
304    gas_price: u64,
305) -> VerifiedTransaction {
306    let tx = Transaction::new_transfer(
307        recipient,
308        object_ref,
309        sender,
310        gas_object_ref,
311        gas_budget,
312        gas_price,
313    );
314    let tx = to_sender_signed_transaction(tx, secret);
315    authority_state
316        .epoch_store_for_testing()
317        .verify_transaction(tx)
318        .unwrap()
319}
320
321pub fn init_certified_transfer_transaction(
322    sender: Address,
323    secret: &AccountPrivateKey,
324    recipient: Address,
325    object_ref: ObjectReference,
326    gas_object_ref: ObjectReference,
327    authority_state: &AuthorityState,
328) -> VerifiedCertificate {
329    let rgp = authority_state.reference_gas_price_for_testing().unwrap();
330    let transfer_transaction = init_transfer_transaction(
331        authority_state,
332        sender,
333        secret,
334        recipient,
335        object_ref,
336        gas_object_ref,
337        rgp * TEST_ONLY_GAS_UNIT_FOR_TRANSFER,
338        rgp,
339    );
340    init_certified_transaction(transfer_transaction.into(), authority_state)
341}
342
343/// With the P-COOL flow, the transaction is also assigned to a new commit,
344/// as the consensus handler assigns every transaction it sequences before
345/// it can execute.
346pub fn init_certified_transaction(
347    transaction: TransactionEnvelope,
348    authority_state: &AuthorityState,
349) -> VerifiedCertificate {
350    let epoch_store = authority_state.epoch_store_for_testing();
351    let transaction = epoch_store.verify_transaction(transaction).unwrap();
352    epoch_store.assign_to_a_new_commit_for_testing(&[transaction.key()]);
353
354    let vote = VerifiedSignedTransaction::new(
355        0,
356        transaction.clone(),
357        authority_state.name,
358        &*authority_state.secret,
359    );
360    CertifiedTransaction::new(
361        transaction.into_message(),
362        vec![vote.auth_sig().clone()],
363        epoch_store.committee(),
364    )
365    .unwrap()
366    .try_into_verified_for_testing(
367        epoch_store.committee(),
368        &VerifyParams::from_protocol_config(epoch_store.protocol_config()),
369    )
370    .unwrap()
371}
372
373pub async fn certify_shared_obj_transaction_no_execution(
374    authority: &AuthorityState,
375    transaction: TransactionEnvelope,
376) -> Result<(VerifiedCertificate, AssignedVersions), IotaError> {
377    let epoch_store = authority.load_epoch_store_one_call_per_task();
378    let transaction = epoch_store.verify_transaction(transaction).unwrap();
379    let response = authority
380        .handle_transaction(&epoch_store, transaction.clone())
381        .await?;
382    let vote = response.status.into_signed_for_testing();
383
384    // Collect signatures from a quorum of authorities
385    let committee = authority.clone_committee_for_testing();
386    let certificate =
387        CertifiedTransaction::new(transaction.into_message(), vec![vote.clone()], &committee)
388            .unwrap()
389            .try_into_verified_for_testing(
390                &committee,
391                &VerifyParams::from_protocol_config(epoch_store.protocol_config()),
392            )
393            .unwrap();
394
395    let assigned_versions = send_consensus_no_execution(authority, &certificate).await;
396
397    Ok((certificate, assigned_versions))
398}
399
400pub async fn enqueue_all_and_execute_all(
401    authority: &AuthorityState,
402    certificates: Vec<(VerifiedCertificate, ExecutionEnv)>,
403) -> Result<Vec<TransactionEffects>, IotaError> {
404    authority.execution_scheduler().enqueue(
405        certificates
406            .iter()
407            .map(|(cert, env)| {
408                (
409                    VerifiedExecutableTransaction::new_from_certificate(cert.clone()).into(),
410                    env.clone(),
411                )
412            })
413            .collect(),
414        &authority.epoch_store_for_testing(),
415    );
416    let mut output = Vec::new();
417    for (cert, _) in certificates {
418        let effects = authority.notify_read_effects("", &cert).await?;
419        output.push(effects);
420    }
421    Ok(output)
422}
423
424pub async fn execute_sequenced_certificate_to_effects(
425    authority: &AuthorityState,
426    certificate: VerifiedCertificate,
427    assigned_versions: AssignedVersions,
428) -> Result<(TransactionEffects, Option<ExecutionError>), IotaError> {
429    let env = ExecutionEnv::new().with_assigned_versions(assigned_versions);
430    authority
431        .epoch_store_for_testing()
432        .assign_to_a_new_commit_for_testing(&[certificate.key()]);
433    authority.execution_scheduler().enqueue(
434        vec![(
435            VerifiedExecutableTransaction::new_from_certificate(certificate.clone()).into(),
436            env.clone(),
437        )],
438        &authority.epoch_store_for_testing(),
439    );
440
441    let (result, execution_error_opt) = authority.try_execute_for_test(&certificate, env)?;
442    let effects = result.inner().data().clone();
443    Ok((effects, execution_error_opt))
444}
445
446pub async fn send_consensus(
447    authority: &AuthorityState,
448    cert: &VerifiedCertificate,
449) -> AssignedVersions {
450    let transaction = SequencedConsensusTransaction::new_test(
451        ConsensusTransaction::new_certificate_message(&authority.name, cert.clone().into_inner()),
452    );
453
454    let (_, assigned_versions) = authority
455        .epoch_store_for_testing()
456        .process_consensus_transactions_for_tests(
457            vec![transaction],
458            &Arc::new(CheckpointServiceNoop {}),
459            authority.get_object_cache_reader().as_ref(),
460            &authority.metrics,
461            true,
462            authority,
463        )
464        .await
465        .unwrap();
466
467    let assigned_versions = assigned_versions
468        .into_map()
469        .remove(&cert.key())
470        .unwrap_or_default();
471
472    let certs = vec![(
473        VerifiedExecutableTransaction::new_from_certificate(cert.clone()),
474        ExecutionEnv::new().with_assigned_versions(assigned_versions.clone()),
475    )];
476
477    let epoch_store = authority.epoch_store_for_testing();
478    epoch_store.assign_to_a_new_commit_for_testing(&[cert.key()]);
479    authority
480        .execution_scheduler()
481        .enqueue_transactions(certs, &epoch_store);
482
483    assigned_versions
484}
485
486/// For a certificate with shared inputs: the commit must assign it versions.
487pub async fn send_consensus_no_execution(
488    authority: &AuthorityState,
489    cert: &VerifiedCertificate,
490) -> AssignedVersions {
491    let transaction = SequencedConsensusTransaction::new_test(
492        ConsensusTransaction::new_certificate_message(&authority.name, cert.clone().into_inner()),
493    );
494
495    // Call process_consensus_transaction() instead of
496    // handle_consensus_transaction(), to avoid actually executing cert.
497    // This allows testing cert execution independently.
498    let (_, assigned_versions) = authority
499        .epoch_store_for_testing()
500        .process_consensus_transactions_for_tests(
501            vec![transaction],
502            &Arc::new(CheckpointServiceNoop {}),
503            authority.get_object_cache_reader().as_ref(),
504            &authority.metrics,
505            true,
506            authority,
507        )
508        .await
509        .unwrap();
510    assert_eq!(assigned_versions.0.len(), 1);
511    assigned_versions
512        .into_map()
513        .remove(&cert.key())
514        .expect("the commit must have assigned versions for this certificate")
515}
516
517pub async fn send_batch_consensus_no_execution(
518    authority: &AuthorityState,
519    certificates: &[VerifiedCertificate],
520    skip_consensus_commit_prologue_in_test: bool,
521) -> (Vec<Schedulable>, AssignedTxAndVersions) {
522    let transactions = certificates
523        .iter()
524        .map(|cert| {
525            SequencedConsensusTransaction::new_test(ConsensusTransaction::new_certificate_message(
526                &authority.name,
527                cert.clone().into_inner(),
528            ))
529        })
530        .collect();
531
532    // Call process_consensus_transaction() instead of
533    // handle_consensus_transaction(), to avoid actually executing cert.
534    // This allows testing cert execution independently.
535    let (scheduled, assigned_versions) = authority
536        .epoch_store_for_testing()
537        .process_consensus_transactions_for_tests(
538            transactions,
539            &Arc::new(CheckpointServiceNoop {}),
540            authority.get_object_cache_reader().as_ref(),
541            &authority.metrics,
542            skip_consensus_commit_prologue_in_test,
543            authority,
544        )
545        .await
546        .unwrap();
547    (drop_attestations(scheduled), assigned_versions)
548}
549
550/// The scheduled transactions without their attestations, for tests that
551/// hand them to the scheduler's plain interface.
552pub fn drop_attestations(scheduled: Vec<SchedulableAttestedTransaction>) -> Vec<Schedulable> {
553    scheduled
554        .into_iter()
555        .map(|scheduled| match scheduled {
556            Schedulable::Transaction(attested) => Schedulable::Transaction(attested.into_parts().0),
557            Schedulable::RandomnessStateUpdate(epoch, round) => {
558                Schedulable::RandomnessStateUpdate(epoch, round)
559            }
560        })
561        .collect()
562}
563
564/// Builds the randomness state update for `round`, persists it and records its
565/// key, which is what `RandomnessRoundReceiver` does before it notifies.
566pub fn resolve_randomness_round(
567    state: &AuthorityState,
568    epoch_store: &AuthorityPerEpochStore,
569    round: u64,
570) -> VerifiedExecutableTransaction {
571    let transaction = make_randomness_state_update(epoch_store, round);
572    state.get_cache_commit().persist_transaction(&transaction);
573    epoch_store
574        .insert_tx_key(
575            TransactionKey::RandomnessRound(epoch_store.epoch(), RandomnessRound::new(round)),
576            *transaction.digest(),
577        )
578        .unwrap();
579    transaction
580}
581
582pub fn randomness_assigned_versions(
583    epoch_store: &AuthorityPerEpochStore,
584) -> Vec<VersionAssignment> {
585    vec![VersionAssignment::new(
586        ObjectId::RANDOMNESS_STATE,
587        epoch_store
588            .epoch_start_config()
589            .randomness_obj_initial_shared_version(),
590    )]
591}
592
593/// A randomness state update transaction for `round`, as
594/// `RandomnessRoundReceiver` would build it.
595pub fn make_randomness_state_update(
596    epoch_store: &AuthorityPerEpochStore,
597    round: u64,
598) -> VerifiedExecutableTransaction {
599    VerifiedExecutableTransaction::new_system(
600        VerifiedTransaction::new_randomness_state_update(
601            epoch_store.epoch(),
602            RandomnessRound::new(round),
603            vec![round as u8],
604            epoch_store
605                .epoch_start_config()
606                .randomness_obj_initial_shared_version(),
607        ),
608        epoch_store.epoch(),
609    )
610}