linera_execution/test_utils/
mod.rs

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