linera_execution/test_utils/
mod.rs

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