linera_sdk/service/
test_runtime.rs

1// Copyright (c) Zefchain Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4//! Runtime types to simulate interfacing with the host executing the service.
5
6use std::{
7    collections::{HashMap, VecDeque},
8    mem,
9    sync::Mutex,
10};
11
12use linera_base::{
13    abi::{ContractAbi, ServiceAbi},
14    data_types::{Amount, ApplicationDescription, BlockHeight, Timestamp},
15    hex, http,
16    identifiers::{AccountOwner, ApplicationId, ChainId, DataBlobHash},
17};
18
19use crate::{KeyValueStore, Service, ViewStorageContext};
20
21/// The runtime available during execution of a query.
22pub struct MockServiceRuntime<Application>
23where
24    Application: Service,
25{
26    application_parameters: Mutex<Option<Application::Parameters>>,
27    application_id: Mutex<Option<ApplicationId<Application::Abi>>>,
28    application_creator_chain_id: Mutex<Option<ChainId>>,
29    application_descriptions: Mutex<HashMap<ApplicationId, ApplicationDescription>>,
30    chain_id: Mutex<Option<ChainId>>,
31    next_block_height: Mutex<Option<BlockHeight>>,
32    timestamp: Mutex<Option<Timestamp>>,
33    chain_balance: Mutex<Option<Amount>>,
34    owner_balances: Mutex<Option<HashMap<AccountOwner, Amount>>>,
35    query_application_handler: Mutex<Option<QueryApplicationHandler>>,
36    expected_http_requests: Mutex<VecDeque<(http::Request, http::Response)>>,
37    blobs: Mutex<Option<HashMap<DataBlobHash, Vec<u8>>>>,
38    scheduled_operations: Mutex<Vec<Vec<u8>>>,
39    key_value_store: KeyValueStore,
40}
41
42impl<Application> Default for MockServiceRuntime<Application>
43where
44    Application: Service,
45{
46    fn default() -> Self {
47        MockServiceRuntime::new()
48    }
49}
50
51impl<Application> MockServiceRuntime<Application>
52where
53    Application: Service,
54{
55    /// Creates a new [`MockServiceRuntime`] instance for a service.
56    pub fn new() -> Self {
57        MockServiceRuntime {
58            application_parameters: Mutex::new(None),
59            application_id: Mutex::new(None),
60            application_creator_chain_id: Mutex::new(None),
61            application_descriptions: Mutex::new(HashMap::new()),
62            chain_id: Mutex::new(None),
63            next_block_height: Mutex::new(None),
64            timestamp: Mutex::new(None),
65            chain_balance: Mutex::new(None),
66            owner_balances: Mutex::new(None),
67            query_application_handler: Mutex::new(None),
68            expected_http_requests: Mutex::new(VecDeque::new()),
69            blobs: Mutex::new(None),
70            scheduled_operations: Mutex::new(vec![]),
71            key_value_store: KeyValueStore::mock(),
72        }
73    }
74
75    /// Returns the key-value store to interface with storage.
76    pub fn key_value_store(&self) -> KeyValueStore {
77        self.key_value_store.clone()
78    }
79
80    /// Returns a storage context suitable for a root view.
81    pub fn root_view_storage_context(&self) -> ViewStorageContext {
82        ViewStorageContext::new_unchecked(self.key_value_store(), Vec::new(), ())
83    }
84
85    /// Configures the application parameters to return during the test.
86    pub fn with_application_parameters(
87        self,
88        application_parameters: Application::Parameters,
89    ) -> Self {
90        *self.application_parameters.lock().unwrap() = Some(application_parameters);
91        self
92    }
93
94    /// Configures the application parameters to return during the test.
95    pub fn set_application_parameters(
96        &self,
97        application_parameters: Application::Parameters,
98    ) -> &Self {
99        *self.application_parameters.lock().unwrap() = Some(application_parameters);
100        self
101    }
102
103    /// Returns the application parameters provided when the application was created.
104    pub fn application_parameters(&self) -> Application::Parameters {
105        Self::fetch_mocked_value(
106            &self.application_parameters,
107            "Application parameters have not been mocked, \
108            please call `MockServiceRuntime::set_application_parameters` first",
109        )
110    }
111
112    /// Configures the application ID to return during the test.
113    pub fn with_application_id(self, application_id: ApplicationId<Application::Abi>) -> Self {
114        *self.application_id.lock().unwrap() = Some(application_id);
115        self
116    }
117
118    /// Configures the application ID to return during the test.
119    pub fn set_application_id(&self, application_id: ApplicationId<Application::Abi>) -> &Self {
120        *self.application_id.lock().unwrap() = Some(application_id);
121        self
122    }
123
124    /// Returns the ID of the current application.
125    pub fn application_id(&self) -> ApplicationId<Application::Abi> {
126        Self::fetch_mocked_value(
127            &self.application_id,
128            "Application ID has not been mocked, \
129            please call `MockServiceRuntime::set_application_id` first",
130        )
131    }
132
133    /// Configures the application creator chain ID to return during the test.
134    pub fn with_application_creator_chain_id(self, application_creator_chain_id: ChainId) -> Self {
135        *self.application_creator_chain_id.lock().unwrap() = Some(application_creator_chain_id);
136        self
137    }
138
139    /// Configures the application creator chain ID to return during the test.
140    pub fn set_application_creator_chain_id(&self, application_creator_chain_id: ChainId) -> &Self {
141        *self.application_creator_chain_id.lock().unwrap() = Some(application_creator_chain_id);
142        self
143    }
144
145    /// Returns the chain ID of the current application creator.
146    pub fn application_creator_chain_id(&self) -> ChainId {
147        Self::fetch_mocked_value(
148            &self.application_creator_chain_id,
149            "Application creator chain ID has not been mocked, \
150            please call `MockServiceRuntime::set_application_creator_chain_id` first",
151        )
152    }
153
154    /// Configures the application description to return for a specific application during the test.
155    pub fn with_application_description(
156        self,
157        application_id: ApplicationId,
158        description: ApplicationDescription,
159    ) -> Self {
160        self.application_descriptions
161            .lock()
162            .unwrap()
163            .insert(application_id, description);
164        self
165    }
166
167    /// Configures the application description to return for a specific application during the test.
168    pub fn set_application_description(
169        &self,
170        application_id: ApplicationId,
171        description: ApplicationDescription,
172    ) -> &Self {
173        self.application_descriptions
174            .lock()
175            .unwrap()
176            .insert(application_id, description);
177        self
178    }
179
180    /// Returns the description of the given application.
181    pub fn read_application_description(
182        &self,
183        application_id: ApplicationId,
184    ) -> ApplicationDescription {
185        self.application_descriptions
186            .lock()
187            .unwrap()
188            .get(&application_id)
189            .cloned()
190            .unwrap_or_else(|| {
191                panic!(
192                    "Application description for {application_id:?} has not been mocked, \
193                    please call `MockServiceRuntime::set_application_description` first"
194                )
195            })
196    }
197
198    /// Configures the chain ID to return during the test.
199    pub fn with_chain_id(self, chain_id: ChainId) -> Self {
200        *self.chain_id.lock().unwrap() = Some(chain_id);
201        self
202    }
203
204    /// Configures the chain ID to return during the test.
205    pub fn set_chain_id(&self, chain_id: ChainId) -> &Self {
206        *self.chain_id.lock().unwrap() = Some(chain_id);
207        self
208    }
209
210    /// Returns the ID of the current chain.
211    pub fn chain_id(&self) -> ChainId {
212        Self::fetch_mocked_value(
213            &self.chain_id,
214            "Chain ID has not been mocked, \
215            please call `MockServiceRuntime::set_chain_id` first",
216        )
217    }
218
219    /// Configures the next block height to return during the test.
220    pub fn with_next_block_height(self, next_block_height: BlockHeight) -> Self {
221        *self.next_block_height.lock().unwrap() = Some(next_block_height);
222        self
223    }
224
225    /// Configures the block height to return during the test.
226    pub fn set_next_block_height(&self, next_block_height: BlockHeight) -> &Self {
227        *self.next_block_height.lock().unwrap() = Some(next_block_height);
228        self
229    }
230
231    /// Returns the height of the next block that can be added to the current chain.
232    pub fn next_block_height(&self) -> BlockHeight {
233        Self::fetch_mocked_value(
234            &self.next_block_height,
235            "Next block height has not been mocked, \
236            please call `MockServiceRuntime::set_next_block_height` first",
237        )
238    }
239
240    /// Configures the system time to return during the test.
241    pub fn with_system_time(self, timestamp: Timestamp) -> Self {
242        *self.timestamp.lock().unwrap() = Some(timestamp);
243        self
244    }
245
246    /// Configures the system time to return during the test.
247    pub fn set_system_time(&self, timestamp: Timestamp) -> &Self {
248        *self.timestamp.lock().unwrap() = Some(timestamp);
249        self
250    }
251
252    /// Retrieves the current system time, i.e. the timestamp of the block in which this is called.
253    pub fn system_time(&self) -> Timestamp {
254        Self::fetch_mocked_value(
255            &self.timestamp,
256            "System time has not been mocked, \
257            please call `MockServiceRuntime::set_system_time` first",
258        )
259    }
260
261    /// Configures the chain balance to return during the test.
262    pub fn with_chain_balance(self, chain_balance: Amount) -> Self {
263        *self.chain_balance.lock().unwrap() = Some(chain_balance);
264        self
265    }
266
267    /// Configures the chain balance to return during the test.
268    pub fn set_chain_balance(&self, chain_balance: Amount) -> &Self {
269        *self.chain_balance.lock().unwrap() = Some(chain_balance);
270        self
271    }
272
273    /// Returns the current chain balance.
274    pub fn chain_balance(&self) -> Amount {
275        Self::fetch_mocked_value(
276            &self.chain_balance,
277            "Chain balance has not been mocked, \
278            please call `MockServiceRuntime::set_chain_balance` first",
279        )
280    }
281
282    /// Configures the balances on the chain to use during the test.
283    pub fn with_owner_balances(
284        self,
285        owner_balances: impl IntoIterator<Item = (AccountOwner, Amount)>,
286    ) -> Self {
287        *self.owner_balances.lock().unwrap() = Some(owner_balances.into_iter().collect());
288        self
289    }
290
291    /// Configures the balances on the chain to use during the test.
292    pub fn set_owner_balances(
293        &self,
294        owner_balances: impl IntoIterator<Item = (AccountOwner, Amount)>,
295    ) -> &Self {
296        *self.owner_balances.lock().unwrap() = Some(owner_balances.into_iter().collect());
297        self
298    }
299
300    /// Configures the balance of one account on the chain to use during the test.
301    pub fn with_owner_balance(self, owner: AccountOwner, balance: Amount) -> Self {
302        self.set_owner_balance(owner, balance);
303        self
304    }
305
306    /// Configures the balance of one account on the chain to use during the test.
307    pub fn set_owner_balance(&self, owner: AccountOwner, balance: Amount) -> &Self {
308        self.owner_balances
309            .lock()
310            .unwrap()
311            .get_or_insert_with(HashMap::new)
312            .insert(owner, balance);
313        self
314    }
315
316    /// Returns the balance of one of the accounts on this chain.
317    pub fn owner_balance(&self, owner: AccountOwner) -> Amount {
318        self.owner_balances
319            .lock()
320            .unwrap()
321            .as_mut()
322            .and_then(|owner_balances| owner_balances.get(&owner).copied())
323            .unwrap_or_else(|| {
324                panic!(
325                    "Balance for owner {owner} was not mocked, \
326                    please include a balance for them with a call to \
327                    `MockServiceRuntime::set_owner_balance`"
328                )
329            })
330    }
331
332    /// Returns the balances of all accounts on the chain.
333    pub fn owner_balances(&self) -> Vec<(AccountOwner, Amount)> {
334        self.owner_balances
335            .lock()
336            .unwrap()
337            .as_ref()
338            .expect(
339                "Owner balances have not been mocked, \
340                please call `MockServiceRuntime::set_owner_balances` first",
341            )
342            .iter()
343            .map(|(owner, amount)| (*owner, *amount))
344            .collect()
345    }
346
347    /// Returns the owners of accounts on this chain.
348    pub fn balance_owners(&self) -> Vec<AccountOwner> {
349        self.owner_balances
350            .lock()
351            .unwrap()
352            .as_ref()
353            .expect(
354                "Owner balances have not been mocked, \
355                please call `MockServiceRuntime::set_owner_balances` first",
356            )
357            .keys()
358            .copied()
359            .collect()
360    }
361
362    /// Schedules an operation to be included in the block being built.
363    ///
364    /// The operation is specified as an opaque blob of bytes.
365    pub fn schedule_raw_operation(&self, operation: Vec<u8>) {
366        self.scheduled_operations.lock().unwrap().push(operation);
367    }
368
369    /// Schedules an operation to be included in the block being built.
370    ///
371    /// The operation is serialized using the application ABI.
372    pub fn schedule_operation(&self, operation: &<Application::Abi as ContractAbi>::Operation) {
373        let bytes = <Application::Abi as ContractAbi>::serialize_operation(operation)
374            .expect("Failed to serialize application operation");
375
376        self.schedule_raw_operation(bytes);
377    }
378
379    /// Returns the list of operations scheduled since the most recent of:
380    ///
381    /// - the last call to this method;
382    /// - the last call to [`Self::scheduled_operations`];
383    /// - or since the mock runtime was created.
384    pub fn raw_scheduled_operations(&self) -> Vec<Vec<u8>> {
385        mem::take(&mut self.scheduled_operations.lock().unwrap())
386    }
387
388    /// Returns the list of operations scheduled since the most recent of:
389    ///
390    /// - the last call to this method;
391    /// - the last call to [`Self::raw_scheduled_operations`];
392    /// - or since the mock runtime was created.
393    ///
394    /// All operations are deserialized using the application ABI.
395    pub fn scheduled_operations(&self) -> Vec<<Application::Abi as ContractAbi>::Operation> {
396        self.raw_scheduled_operations()
397            .into_iter()
398            .enumerate()
399            .map(|(index, bytes)| {
400                let hex_bytes = hex::encode(&bytes);
401                <Application::Abi as ContractAbi>::deserialize_operation(bytes).unwrap_or_else(
402                    |error| {
403                        panic!(
404                            "Failed to deserialize scheduled operation #{index} (0x{}): {error}",
405                            hex_bytes
406                        )
407                    },
408                )
409            })
410            .collect()
411    }
412
413    /// Configures the handler for application queries made during the test.
414    pub fn with_query_application_handler(
415        self,
416        handler: impl FnMut(ApplicationId, Vec<u8>) -> Vec<u8> + Send + 'static,
417    ) -> Self {
418        *self.query_application_handler.lock().unwrap() = Some(Box::new(handler));
419        self
420    }
421
422    /// Configures the handler for application queries made during the test.
423    pub fn set_query_application_handler(
424        &self,
425        handler: impl FnMut(ApplicationId, Vec<u8>) -> Vec<u8> + Send + 'static,
426    ) -> &Self {
427        *self.query_application_handler.lock().unwrap() = Some(Box::new(handler));
428        self
429    }
430
431    /// Queries another application.
432    pub fn query_application<A: ServiceAbi>(
433        &self,
434        application: ApplicationId<A>,
435        query: &A::Query,
436    ) -> A::QueryResponse {
437        let query_bytes =
438            serde_json::to_vec(&query).expect("Failed to serialize query to another application");
439
440        let mut handler_guard = self.query_application_handler.lock().unwrap();
441        let handler = handler_guard.as_mut().expect(
442            "Handler for `query_application` has not been mocked, \
443            please call `MockServiceRuntime::set_query_application_handler` first",
444        );
445
446        let response_bytes = handler(application.forget_abi(), query_bytes);
447
448        serde_json::from_slice(&response_bytes)
449            .expect("Failed to deserialize query response from application")
450    }
451
452    /// Adds an expected `http_request` call, and the response it should return in the test.
453    pub fn add_expected_http_request(&mut self, request: http::Request, response: http::Response) {
454        self.expected_http_requests
455            .lock()
456            .unwrap()
457            .push_back((request, response));
458    }
459
460    /// Makes an HTTP `request` as an oracle and returns the HTTP response.
461    ///
462    /// Should only be used with queries where it is very likely that all validators will receive
463    /// the same response, otherwise most block proposals will fail.
464    ///
465    /// Cannot be used in fast blocks: A block using this call should be proposed by a regular
466    /// owner, not a super owner.
467    pub fn http_request(&self, request: http::Request) -> http::Response {
468        let maybe_request = self.expected_http_requests.lock().unwrap().pop_front();
469        let (expected_request, response) = maybe_request.expect("Unexpected HTTP request");
470        assert_eq!(request, expected_request);
471        response
472    }
473
474    /// Configures the `blobs` returned when fetching from hashes during the test.
475    pub fn with_blobs(self, blobs: impl IntoIterator<Item = (DataBlobHash, Vec<u8>)>) -> Self {
476        *self.blobs.lock().unwrap() = Some(blobs.into_iter().collect());
477        self
478    }
479
480    /// Configures the `blobs` returned when fetching from hashes during the test.
481    pub fn set_blobs(&self, blobs: impl IntoIterator<Item = (DataBlobHash, Vec<u8>)>) -> &Self {
482        *self.blobs.lock().unwrap() = Some(blobs.into_iter().collect());
483        self
484    }
485
486    /// Configures the `blob` returned when fetching from the given hash during the test.
487    pub fn with_blob(self, hash: impl Into<DataBlobHash>, blob: Vec<u8>) -> Self {
488        self.set_blob(hash, blob);
489        self
490    }
491
492    /// Configures the `blob` returned when fetching from the hash during the test.
493    pub fn set_blob(&self, hash: impl Into<DataBlobHash>, blob: Vec<u8>) -> &Self {
494        self.blobs
495            .lock()
496            .unwrap()
497            .get_or_insert_with(HashMap::new)
498            .insert(hash.into(), blob);
499        self
500    }
501
502    /// Fetches a blob from a given hash.
503    pub fn read_data_blob(&self, hash: DataBlobHash) -> Vec<u8> {
504        self.blobs
505            .lock()
506            .unwrap()
507            .as_ref()
508            .and_then(|blobs| blobs.get(&hash).cloned())
509            .unwrap_or_else(|| {
510                panic!(
511                    "Blob for hash {hash:?} has not been mocked, \
512                    please call `MockServiceRuntime::set_blob` first"
513                )
514            })
515    }
516
517    /// Asserts that a blob with the given hash exists in storage.
518    pub fn assert_blob_exists(&self, hash: DataBlobHash) {
519        assert!(
520            self.blobs
521                .lock()
522                .unwrap()
523                .as_ref()
524                .is_some_and(|blobs| blobs.contains_key(&hash)),
525            "Blob for hash {hash:?} has not been mocked, \
526            please call `MockServiceRuntime::set_blob` first"
527        );
528    }
529
530    /// Loads a mocked value from the `slot` cache or panics with a provided `message`.
531    fn fetch_mocked_value<T>(slot: &Mutex<Option<T>>, message: &str) -> T
532    where
533        T: Clone,
534    {
535        slot.lock().unwrap().clone().expect(message)
536    }
537}
538
539/// A type alias for the handler for application queries.
540pub type QueryApplicationHandler = Box<dyn FnMut(ApplicationId, Vec<u8>) -> Vec<u8> + Send>;