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