Skip to main content

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