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, CryptoHash, 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        balance,
60        epoch: Epoch::ZERO,
61        ownership,
62    };
63    ChainDescription::new(origin, config, Timestamp::default())
64}
65
66/// Creates a dummy chain description owned by the given owner, with the maximum balance.
67pub fn dummy_chain_description_with_owner(index: u32, owner: AccountOwner) -> ChainDescription {
68    dummy_chain_description_with_ownership_and_balance(
69        index,
70        ChainOwnership::single(owner),
71        Amount::MAX,
72    )
73}
74
75/// Creates a dummy chain description with a deterministic single owner and the maximum balance.
76pub fn dummy_chain_description(index: u32) -> ChainDescription {
77    let chain_key = AccountPublicKey::test_key(2 * (index % 128) as u8 + 1);
78    let ownership = ChainOwnership::single(chain_key.into());
79    dummy_chain_description_with_ownership_and_balance(index, ownership, Amount::MAX)
80}
81
82/// Creates a dummy [`ApplicationDescription`] for use in tests.
83pub fn create_dummy_user_application_description(
84    index: u32,
85) -> (ApplicationDescription, Blob, Blob) {
86    let chain_id = dummy_chain_description(1).id();
87    let mut contract_bytes = b"contract".to_vec();
88    let mut service_bytes = b"service".to_vec();
89    contract_bytes.push(index as u8);
90    service_bytes.push(index as u8);
91    let contract_blob = Blob::new_contract_bytecode(CompressedBytecode {
92        compressed_bytes: Arc::new(contract_bytes.into_boxed_slice()),
93    });
94    let service_blob = Blob::new_service_bytecode(CompressedBytecode {
95        compressed_bytes: Arc::new(service_bytes.into_boxed_slice()),
96    });
97
98    let vm_runtime = VmRuntime::Wasm;
99    (
100        ApplicationDescription {
101            module_id: ModuleId::new(contract_blob.id().hash, service_blob.id().hash, vm_runtime),
102            creator_chain_id: chain_id,
103            block_height: 0.into(),
104            application_index: index,
105            required_application_ids: vec![],
106            parameters: vec![],
107        },
108        contract_blob,
109        service_blob,
110    )
111}
112
113/// Creates a dummy [`OperationContext`] to use in tests.
114pub fn create_dummy_operation_context(chain_id: ChainId) -> OperationContext {
115    OperationContext {
116        chain_id,
117        height: BlockHeight(0),
118        round: Some(0),
119        authenticated_owner: None,
120        timestamp: Default::default(),
121    }
122}
123
124/// Creates a dummy [`MessageContext`] to use in tests.
125pub fn create_dummy_message_context(
126    chain_id: ChainId,
127    authenticated_owner: Option<AccountOwner>,
128) -> MessageContext {
129    MessageContext {
130        chain_id,
131        origin: chain_id,
132        origin_certificate_hash: CryptoHash::default(),
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}