linera_execution/test_utils/
mod.rs

1// Copyright (c) Zefchain Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4#![allow(clippy::cast_possible_truncation)]
5
6mod mock_application;
7#[cfg(with_revm)]
8pub mod solidity;
9mod system_execution_state;
10
11use std::{collections::BTreeMap, sync::Arc, thread, vec};
12
13use linera_base::{
14    crypto::{AccountPublicKey, ValidatorPublicKey},
15    data_types::{
16        Amount, Blob, BlockHeight, ChainDescription, ChainOrigin, CompressedBytecode, Epoch,
17        InitialChainConfig, OracleResponse, Timestamp,
18    },
19    identifiers::{AccountOwner, ApplicationId, BlobId, ChainId, ModuleId},
20    ownership::ChainOwnership,
21    vm::VmRuntime,
22};
23use linera_views::{context::Context, views::View};
24use proptest::{prelude::any, strategy::Strategy};
25
26pub use self::{
27    mock_application::{ExpectedCall, MockApplication, MockApplicationInstance},
28    system_execution_state::SystemExecutionState,
29};
30use crate::{
31    committee::Committee, ApplicationDescription, ExecutionRuntimeContext, ExecutionStateView,
32    MessageContext, OperationContext, QueryContext, ServiceRuntimeEndpoint, ServiceSyncRuntime,
33    SystemExecutionStateView,
34};
35
36pub fn dummy_committee() -> Committee {
37    Committee::make_simple(vec![(
38        ValidatorPublicKey::test_key(0),
39        AccountPublicKey::test_key(0),
40    )])
41}
42
43pub fn dummy_committees() -> BTreeMap<Epoch, Committee> {
44    let committee = dummy_committee();
45    BTreeMap::from([(Epoch::ZERO, committee)])
46}
47
48pub fn dummy_chain_description_with_ownership_and_balance(
49    index: u32,
50    ownership: ChainOwnership,
51    balance: Amount,
52) -> ChainDescription {
53    let origin = ChainOrigin::Root(index);
54    let config = InitialChainConfig {
55        application_permissions: Default::default(),
56        balance,
57        epoch: Epoch::ZERO,
58        ownership,
59    };
60    ChainDescription::new(origin, config, Timestamp::default())
61}
62
63pub fn dummy_chain_description_with_owner(index: u32, owner: AccountOwner) -> ChainDescription {
64    dummy_chain_description_with_ownership_and_balance(
65        index,
66        ChainOwnership::single(owner),
67        Amount::MAX,
68    )
69}
70
71pub fn dummy_chain_description(index: u32) -> ChainDescription {
72    let chain_key = AccountPublicKey::test_key(2 * (index % 128) as u8 + 1);
73    let ownership = ChainOwnership::single(chain_key.into());
74    dummy_chain_description_with_ownership_and_balance(index, ownership, Amount::MAX)
75}
76
77/// Creates a dummy [`ApplicationDescription`] for use in tests.
78pub fn create_dummy_user_application_description(
79    index: u32,
80) -> (ApplicationDescription, Blob, Blob) {
81    let chain_id = dummy_chain_description(1).id();
82    let mut contract_bytes = b"contract".to_vec();
83    let mut service_bytes = b"service".to_vec();
84    contract_bytes.push(index as u8);
85    service_bytes.push(index as u8);
86    let contract_blob = Blob::new_contract_bytecode(CompressedBytecode {
87        compressed_bytes: Arc::new(contract_bytes.into_boxed_slice()),
88    });
89    let service_blob = Blob::new_service_bytecode(CompressedBytecode {
90        compressed_bytes: Arc::new(service_bytes.into_boxed_slice()),
91    });
92
93    let vm_runtime = VmRuntime::Wasm;
94    (
95        ApplicationDescription {
96            module_id: ModuleId::new(contract_blob.id().hash, service_blob.id().hash, vm_runtime),
97            creator_chain_id: chain_id,
98            block_height: 0.into(),
99            application_index: index,
100            required_application_ids: vec![],
101            parameters: vec![],
102        },
103        contract_blob,
104        service_blob,
105    )
106}
107
108/// Creates a dummy [`OperationContext`] to use in tests.
109pub fn create_dummy_operation_context(chain_id: ChainId) -> OperationContext {
110    OperationContext {
111        chain_id,
112        height: BlockHeight(0),
113        round: Some(0),
114        authenticated_owner: None,
115        timestamp: Default::default(),
116    }
117}
118
119/// Creates a dummy [`MessageContext`] to use in tests.
120pub fn create_dummy_message_context(
121    chain_id: ChainId,
122    authenticated_owner: Option<AccountOwner>,
123) -> MessageContext {
124    MessageContext {
125        chain_id,
126        origin: chain_id,
127        origin_timestamp: Default::default(),
128        is_bouncing: false,
129        authenticated_owner,
130        refund_grant_to: None,
131        height: BlockHeight(0),
132        round: Some(0),
133        timestamp: Default::default(),
134    }
135}
136
137/// Creates a dummy [`QueryContext`] to use in tests.
138pub fn create_dummy_query_context() -> QueryContext {
139    QueryContext {
140        chain_id: dummy_chain_description(0).id(),
141        next_block_height: BlockHeight(0),
142        local_time: Timestamp::from(0),
143    }
144}
145
146/// Registration of [`MockApplication`]s to use in tests.
147#[allow(async_fn_in_trait)]
148pub trait RegisterMockApplication {
149    /// Returns the chain to use for the creation of the application.
150    ///
151    /// This is included in the mocked [`ApplicationId`].
152    async fn creator_chain_id(&self) -> ChainId;
153
154    /// Registers a new [`MockApplication`] and returns it with the [`ApplicationId`] that was
155    /// used for it.
156    async fn register_mock_application(
157        &mut self,
158        index: u32,
159    ) -> anyhow::Result<(ApplicationId, MockApplication, [BlobId; 3])> {
160        let (description, contract, service) = create_dummy_user_application_description(index);
161        let description_blob_id = Blob::new_application_description(&description).id();
162        let contract_blob_id = contract.id();
163        let service_blob_id = service.id();
164
165        let (app_id, application) = self
166            .register_mock_application_with(description, contract, service)
167            .await?;
168        Ok((
169            app_id,
170            application,
171            [description_blob_id, contract_blob_id, service_blob_id],
172        ))
173    }
174
175    /// Registers a new [`MockApplication`] associated with a [`ApplicationDescription`] and
176    /// its bytecode [`Blob`]s.
177    async fn register_mock_application_with(
178        &mut self,
179        description: ApplicationDescription,
180        contract: Blob,
181        service: Blob,
182    ) -> anyhow::Result<(ApplicationId, MockApplication)>;
183}
184
185impl<C> RegisterMockApplication for ExecutionStateView<C>
186where
187    C: Context + Clone + Send + Sync + 'static,
188    C::Extra: ExecutionRuntimeContext,
189{
190    async fn creator_chain_id(&self) -> ChainId {
191        self.system.creator_chain_id().await
192    }
193
194    async fn register_mock_application_with(
195        &mut self,
196        description: ApplicationDescription,
197        contract: Blob,
198        service: Blob,
199    ) -> anyhow::Result<(ApplicationId, MockApplication)> {
200        self.system
201            .register_mock_application_with(description, contract, service)
202            .await
203    }
204}
205
206impl<C> RegisterMockApplication for SystemExecutionStateView<C>
207where
208    C: Context + Clone + Send + Sync + 'static,
209    C::Extra: ExecutionRuntimeContext,
210{
211    async fn creator_chain_id(&self) -> ChainId {
212        self.description.get().await.expect("failed to load description").as_ref().expect(
213            "Can't register applications on a system state with no associated `ChainDescription`",
214        ).into()
215    }
216
217    async fn register_mock_application_with(
218        &mut self,
219        description: ApplicationDescription,
220        contract: Blob,
221        service: Blob,
222    ) -> anyhow::Result<(ApplicationId, MockApplication)> {
223        let id = From::from(&description);
224        let context = self.context();
225        let extra = context.extra();
226        let mock_application = MockApplication::default();
227
228        extra
229            .user_contracts()
230            .pin()
231            .insert(id, mock_application.clone().into());
232        extra
233            .user_services()
234            .pin()
235            .insert(id, mock_application.clone().into());
236        extra
237            .add_blobs([
238                contract,
239                service,
240                Blob::new_application_description(&description),
241            ])
242            .await?;
243
244        Ok((id, mock_application))
245    }
246}
247
248pub fn create_dummy_user_application_registrations(
249    count: u32,
250) -> anyhow::Result<Vec<(ApplicationId, ApplicationDescription, Blob, Blob)>> {
251    let mut ids = Vec::with_capacity(count as usize);
252
253    for index in 0..count {
254        let (description, contract_blob, service_blob) =
255            create_dummy_user_application_description(index);
256        let id = From::from(&description);
257
258        ids.push((id, description, contract_blob, service_blob));
259    }
260
261    Ok(ids)
262}
263
264impl QueryContext {
265    /// Spawns a thread running the [`ServiceSyncRuntime`] actor.
266    ///
267    /// Returns the endpoints to communicate with the actor.
268    pub fn spawn_service_runtime_actor(self) -> ServiceRuntimeEndpoint {
269        let (execution_state_sender, incoming_execution_requests) =
270            futures::channel::mpsc::unbounded();
271        let (runtime_request_sender, runtime_request_receiver) = std::sync::mpsc::channel();
272
273        thread::spawn(move || {
274            ServiceSyncRuntime::new(execution_state_sender, self).run(&runtime_request_receiver)
275        });
276
277        ServiceRuntimeEndpoint {
278            incoming_execution_requests,
279            runtime_request_sender,
280        }
281    }
282}
283
284/// Creates a [`Strategy`] for creating a [`BTreeMap`] of [`AccountOwner`]s with an initial
285/// non-zero [`Amount`] of tokens.
286pub fn test_accounts_strategy() -> impl Strategy<Value = BTreeMap<AccountOwner, Amount>> {
287    proptest::collection::btree_map(
288        any::<AccountOwner>(),
289        (1_u128..).prop_map(Amount::from_tokens),
290        0..5,
291    )
292}
293
294/// Creates a vector of [`OracleResponse`]s for the supplied [`BlobId`]s.
295pub fn blob_oracle_responses<'a>(blobs: impl Iterator<Item = &'a BlobId>) -> Vec<OracleResponse> {
296    blobs
297        .into_iter()
298        .copied()
299        .map(OracleResponse::Blob)
300        .collect()
301}