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_types::{
10    crypto::{AccountKeyPair, AuthorityKeyPair},
11    messages_consensus::ConsensusTransaction,
12    utils::to_sender_signed_transaction,
13};
14
15use super::{test_authority_builder::TestAuthorityBuilder, *};
16use crate::{checkpoints::CheckpointServiceNoop, consensus_handler::SequencedConsensusTransaction};
17
18pub async fn send_and_confirm_transaction(
19    authority: &AuthorityState,
20    transaction: Transaction,
21) -> Result<(CertifiedTransaction, SignedTransactionEffects), IotaError> {
22    send_and_confirm_transaction_(
23        authority,
24        None, // no fullnode_key_pair
25        transaction,
26        false, // no shared objects
27    )
28    .await
29}
30pub async fn send_and_confirm_transaction_(
31    authority: &AuthorityState,
32    fullnode: Option<&AuthorityState>,
33    transaction: Transaction,
34    with_shared: bool, // transaction includes shared objects
35) -> Result<(CertifiedTransaction, SignedTransactionEffects), IotaError> {
36    let (txn, effects, _execution_error_opt) = send_and_confirm_transaction_with_execution_error(
37        authority,
38        fullnode,
39        transaction,
40        with_shared,
41        true,
42    )
43    .await?;
44    Ok((txn, effects))
45}
46
47pub async fn certify_transaction(
48    authority: &AuthorityState,
49    transaction: Transaction,
50) -> Result<VerifiedCertificate, IotaError> {
51    // Make the initial request
52    let epoch_store = authority.load_epoch_store_one_call_per_task();
53    // TODO: Move this check to a more appropriate place.
54    transaction.validity_check(epoch_store.protocol_config(), epoch_store.epoch())?;
55    let transaction = epoch_store.verify_transaction(transaction).unwrap();
56
57    let response = authority
58        .handle_transaction(&epoch_store, transaction.clone())
59        .await?;
60    let vote = response.status.into_signed_for_testing();
61
62    // Collect signatures from a quorum of authorities
63    let committee = authority.clone_committee_for_testing();
64    let certificate = CertifiedTransaction::new(transaction.into_message(), vec![vote], &committee)
65        .unwrap()
66        .try_into_verified_for_testing(&committee, &Default::default())
67        .unwrap();
68    Ok(certificate)
69}
70
71pub async fn execute_certificate_with_execution_error(
72    authority: &AuthorityState,
73    fullnode: Option<&AuthorityState>,
74    certificate: VerifiedCertificate,
75    with_shared: bool, // transaction includes shared objects
76    fake_consensus: bool,
77) -> Result<
78    (
79        CertifiedTransaction,
80        SignedTransactionEffects,
81        Option<ExecutionError>,
82    ),
83    IotaError,
84> {
85    // We also check the incremental effects of the transaction on the live object
86    // set against StateAccumulator for testing and regression detection.
87    // We must do this before sending to consensus, otherwise consensus may already
88    // lead to transaction execution and state change.
89    let state_acc = StateAccumulator::new_for_tests(authority.get_accumulator_store().clone());
90    let mut state = state_acc.accumulate_cached_live_object_set_for_testing();
91
92    if with_shared {
93        if fake_consensus {
94            send_consensus(authority, &certificate).await;
95        } else {
96            // Just set object locks directly if send_consensus is not requested.
97            authority
98                .epoch_store_for_testing()
99                .assign_shared_object_versions_for_tests(
100                    authority.get_object_cache_reader().as_ref(),
101                    &vec![VerifiedExecutableTransaction::new_from_certificate(
102                        certificate.clone(),
103                    )],
104                )?;
105        }
106        if let Some(fullnode) = fullnode {
107            fullnode
108                .epoch_store_for_testing()
109                .assign_shared_object_versions_for_tests(
110                    fullnode.get_object_cache_reader().as_ref(),
111                    &vec![VerifiedExecutableTransaction::new_from_certificate(
112                        certificate.clone(),
113                    )],
114                )?;
115        }
116    }
117
118    // Submit the confirmation. *Now* execution actually happens, and it should fail
119    // when we try to look up our dummy module. we unfortunately don't get a
120    // very descriptive error message, but we can at least see that something went
121    // wrong inside the VM
122    let (result, execution_error_opt) = authority.try_execute_for_test(&certificate)?;
123    let state_after = state_acc.accumulate_cached_live_object_set_for_testing();
124    let effects_acc = state_acc.accumulate_effects(&[result.inner().data().clone()]);
125    state.union(&effects_acc);
126
127    assert_eq!(state_after.digest(), state.digest());
128
129    if let Some(fullnode) = fullnode {
130        fullnode.try_execute_for_test(&certificate)?;
131    }
132    Ok((
133        certificate.into_inner(),
134        result.into_inner(),
135        execution_error_opt,
136    ))
137}
138
139pub async fn send_and_confirm_transaction_with_execution_error(
140    authority: &AuthorityState,
141    fullnode: Option<&AuthorityState>,
142    transaction: Transaction,
143    with_shared: bool,    // transaction includes shared objects
144    fake_consensus: bool, // runs consensus handler if true
145) -> Result<
146    (
147        CertifiedTransaction,
148        SignedTransactionEffects,
149        Option<ExecutionError>,
150    ),
151    IotaError,
152> {
153    let certificate = certify_transaction(authority, transaction).await?;
154    execute_certificate_with_execution_error(
155        authority,
156        fullnode,
157        certificate,
158        with_shared,
159        fake_consensus,
160    )
161    .await
162}
163
164pub async fn init_state_validator_with_fullnode() -> (Arc<AuthorityState>, Arc<AuthorityState>) {
165    use iota_types::crypto::get_authority_key_pair;
166
167    let validator = TestAuthorityBuilder::new().build().await;
168    let fullnode_key_pair = get_authority_key_pair().1;
169    let fullnode = TestAuthorityBuilder::new()
170        .with_keypair(&fullnode_key_pair)
171        .build()
172        .await;
173    (validator, fullnode)
174}
175
176pub async fn init_state_with_committee(
177    genesis: &Genesis,
178    authority_key: &AuthorityKeyPair,
179) -> Arc<AuthorityState> {
180    TestAuthorityBuilder::new()
181        .with_genesis_and_keypair(genesis, authority_key)
182        .build()
183        .await
184}
185
186pub async fn init_state_with_ids<I: IntoIterator<Item = (IotaAddress, ObjectID)>>(
187    objects: I,
188) -> Arc<AuthorityState> {
189    let state = TestAuthorityBuilder::new().build().await;
190    for (address, object_id) in objects {
191        let obj = Object::with_id_owner_for_testing(object_id, address);
192        // TODO: Make this part of genesis initialization instead of explicit insert.
193        state.insert_genesis_object(obj).await;
194    }
195    state
196}
197
198pub async fn init_state_with_ids_and_versions<
199    I: IntoIterator<Item = (IotaAddress, ObjectID, SequenceNumber)>,
200>(
201    objects: I,
202) -> Arc<AuthorityState> {
203    let state = TestAuthorityBuilder::new().build().await;
204    for (address, object_id, version) in objects {
205        let obj = Object::with_id_owner_version_for_testing(
206            object_id,
207            version,
208            Owner::AddressOwner(address),
209        );
210        state.insert_genesis_object(obj).await;
211    }
212    state
213}
214
215pub async fn init_state_with_objects<I: IntoIterator<Item = Object>>(
216    objects: I,
217) -> Arc<AuthorityState> {
218    let dir = tempfile::TempDir::new().unwrap();
219    let network_config =
220        iota_swarm_config::network_config_builder::ConfigBuilder::new(&dir).build();
221    let genesis = network_config.genesis;
222    let keypair = network_config.validator_configs[0]
223        .authority_key_pair()
224        .copy();
225    init_state_with_objects_and_committee(objects, &genesis, &keypair).await
226}
227
228pub async fn init_state_with_objects_and_committee<I: IntoIterator<Item = Object>>(
229    objects: I,
230    genesis: &Genesis,
231    authority_key: &AuthorityKeyPair,
232) -> Arc<AuthorityState> {
233    let state = init_state_with_committee(genesis, authority_key).await;
234    for o in objects {
235        state.insert_genesis_object(o).await;
236    }
237    state
238}
239
240pub async fn init_state_with_object_id(
241    address: IotaAddress,
242    object: ObjectID,
243) -> Arc<AuthorityState> {
244    init_state_with_ids(std::iter::once((address, object))).await
245}
246
247pub async fn init_state_with_ids_and_expensive_checks<
248    I: IntoIterator<Item = (IotaAddress, ObjectID)>,
249>(
250    objects: I,
251    config: ExpensiveSafetyCheckConfig,
252) -> Arc<AuthorityState> {
253    let state = TestAuthorityBuilder::new()
254        .with_expensive_safety_checks(config)
255        .build()
256        .await;
257    for (address, object_id) in objects {
258        let obj = Object::with_id_owner_for_testing(object_id, address);
259        // TODO: Make this part of genesis initialization instead of explicit insert.
260        state.insert_genesis_object(obj).await;
261    }
262    state
263}
264
265pub fn init_transfer_transaction(
266    authority_state: &AuthorityState,
267    sender: IotaAddress,
268    secret: &AccountKeyPair,
269    recipient: IotaAddress,
270    object_ref: ObjectRef,
271    gas_object_ref: ObjectRef,
272    gas_budget: u64,
273    gas_price: u64,
274) -> VerifiedTransaction {
275    let data = TransactionData::new_transfer(
276        recipient,
277        object_ref,
278        sender,
279        gas_object_ref,
280        gas_budget,
281        gas_price,
282    );
283    let tx = to_sender_signed_transaction(data, secret);
284    authority_state
285        .epoch_store_for_testing()
286        .verify_transaction(tx)
287        .unwrap()
288}
289
290pub fn init_certified_transfer_transaction(
291    sender: IotaAddress,
292    secret: &AccountKeyPair,
293    recipient: IotaAddress,
294    object_ref: ObjectRef,
295    gas_object_ref: ObjectRef,
296    authority_state: &AuthorityState,
297) -> VerifiedCertificate {
298    let rgp = authority_state.reference_gas_price_for_testing().unwrap();
299    let transfer_transaction = init_transfer_transaction(
300        authority_state,
301        sender,
302        secret,
303        recipient,
304        object_ref,
305        gas_object_ref,
306        rgp * TEST_ONLY_GAS_UNIT_FOR_TRANSFER,
307        rgp,
308    );
309    init_certified_transaction(transfer_transaction.into(), authority_state)
310}
311
312pub fn init_certified_transaction(
313    transaction: Transaction,
314    authority_state: &AuthorityState,
315) -> VerifiedCertificate {
316    let epoch_store = authority_state.epoch_store_for_testing();
317    let transaction = epoch_store.verify_transaction(transaction).unwrap();
318
319    let vote = VerifiedSignedTransaction::new(
320        0,
321        transaction.clone(),
322        authority_state.name,
323        &*authority_state.secret,
324    );
325    CertifiedTransaction::new(
326        transaction.into_message(),
327        vec![vote.auth_sig().clone()],
328        epoch_store.committee(),
329    )
330    .unwrap()
331    .try_into_verified_for_testing(epoch_store.committee(), &Default::default())
332    .unwrap()
333}
334
335pub async fn certify_shared_obj_transaction_no_execution(
336    authority: &AuthorityState,
337    transaction: Transaction,
338) -> Result<VerifiedCertificate, IotaError> {
339    let epoch_store = authority.load_epoch_store_one_call_per_task();
340    let transaction = epoch_store.verify_transaction(transaction).unwrap();
341    let response = authority
342        .handle_transaction(&epoch_store, transaction.clone())
343        .await?;
344    let vote = response.status.into_signed_for_testing();
345
346    // Collect signatures from a quorum of authorities
347    let committee = authority.clone_committee_for_testing();
348    let certificate =
349        CertifiedTransaction::new(transaction.into_message(), vec![vote.clone()], &committee)
350            .unwrap()
351            .try_into_verified_for_testing(&committee, &Default::default())
352            .unwrap();
353
354    send_consensus_no_execution(authority, &certificate).await;
355
356    Ok(certificate)
357}
358
359pub async fn enqueue_all_and_execute_all(
360    authority: &AuthorityState,
361    certificates: Vec<VerifiedCertificate>,
362) -> Result<Vec<TransactionEffects>, IotaError> {
363    authority.enqueue_certificates_for_execution(
364        certificates.clone(),
365        &authority.epoch_store_for_testing(),
366    );
367    let mut output = Vec::new();
368    for cert in certificates {
369        let effects = authority.notify_read_effects(&cert).await?;
370        output.push(effects);
371    }
372    Ok(output)
373}
374
375pub async fn execute_sequenced_certificate_to_effects(
376    authority: &AuthorityState,
377    certificate: VerifiedCertificate,
378) -> Result<(TransactionEffects, Option<ExecutionError>), IotaError> {
379    authority.enqueue_certificates_for_execution(
380        vec![certificate.clone()],
381        &authority.epoch_store_for_testing(),
382    );
383
384    let (result, execution_error_opt) = authority.try_execute_for_test(&certificate)?;
385    let effects = result.inner().data().clone();
386    Ok((effects, execution_error_opt))
387}
388
389pub async fn send_consensus(authority: &AuthorityState, cert: &VerifiedCertificate) {
390    let transaction = SequencedConsensusTransaction::new_test(
391        ConsensusTransaction::new_certificate_message(&authority.name, cert.clone().into_inner()),
392    );
393
394    let certs = authority
395        .epoch_store_for_testing()
396        .process_consensus_transactions_for_tests(
397            vec![transaction],
398            &Arc::new(CheckpointServiceNoop {}),
399            authority.get_object_cache_reader().as_ref(),
400            authority.get_transaction_cache_reader().as_ref(),
401            &authority.metrics,
402            true,
403        )
404        .await
405        .unwrap();
406
407    authority
408        .transaction_manager()
409        .enqueue(certs, &authority.epoch_store_for_testing());
410}
411
412pub async fn send_consensus_no_execution(authority: &AuthorityState, cert: &VerifiedCertificate) {
413    let transaction = SequencedConsensusTransaction::new_test(
414        ConsensusTransaction::new_certificate_message(&authority.name, cert.clone().into_inner()),
415    );
416
417    // Call process_consensus_transaction() instead of
418    // handle_consensus_transaction(), to avoid actually executing cert.
419    // This allows testing cert execution independently.
420    authority
421        .epoch_store_for_testing()
422        .process_consensus_transactions_for_tests(
423            vec![transaction],
424            &Arc::new(CheckpointServiceNoop {}),
425            authority.get_object_cache_reader().as_ref(),
426            authority.get_transaction_cache_reader().as_ref(),
427            &authority.metrics,
428            true,
429        )
430        .await
431        .unwrap();
432}
433
434pub async fn send_batch_consensus_no_execution(
435    authority: &AuthorityState,
436    certificates: &[VerifiedCertificate],
437    skip_consensus_commit_prologue_in_test: bool,
438) -> Vec<VerifiedExecutableTransaction> {
439    let transactions = certificates
440        .iter()
441        .map(|cert| {
442            SequencedConsensusTransaction::new_test(ConsensusTransaction::new_certificate_message(
443                &authority.name,
444                cert.clone().into_inner(),
445            ))
446        })
447        .collect();
448
449    // Call process_consensus_transaction() instead of
450    // handle_consensus_transaction(), to avoid actually executing cert.
451    // This allows testing cert execution independently.
452    authority
453        .epoch_store_for_testing()
454        .process_consensus_transactions_for_tests(
455            transactions,
456            &Arc::new(CheckpointServiceNoop {}),
457            authority.get_object_cache_reader().as_ref(),
458            authority.get_transaction_cache_reader().as_ref(),
459            &authority.metrics,
460            skip_consensus_commit_prologue_in_test,
461        )
462        .await
463        .unwrap()
464}