Skip to main content

linera_execution/
lib.rs

1// Copyright (c) Zefchain Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4//! This module manages the execution of the system application and the user applications in a
5//! Linera chain.
6
7pub mod committee;
8pub mod evm;
9mod execution;
10pub mod execution_state_actor;
11#[cfg(with_graphql)]
12mod graphql;
13mod policy;
14mod resources;
15mod runtime;
16pub mod system;
17#[cfg(with_testing)]
18pub mod test_utils;
19mod transaction_tracker;
20mod util;
21mod wasm;
22
23use std::{any::Any, collections::BTreeMap, fmt, ops::RangeInclusive, str::FromStr, sync::Arc};
24
25use allocative::Allocative;
26use async_graphql::SimpleObject;
27use async_trait::async_trait;
28use custom_debug_derive::Debug;
29use derive_more::Display;
30#[cfg(web)]
31use js_sys::wasm_bindgen::JsValue;
32use linera_base::{
33    abi::Abi,
34    crypto::{BcsHashable, CryptoHash},
35    data_types::{
36        Amount, ApplicationDescription, ApplicationPermissions, ArithmeticError, Blob, BlockHeight,
37        Bytecode, DecompressionError, Epoch, NetworkDescription, SendMessageRequest, StreamUpdate,
38        Timestamp,
39    },
40    doc_scalar, hex_debug, http,
41    identifiers::{
42        Account, AccountOwner, ApplicationId, BlobId, BlobType, ChainId, DataBlobHash, EventId,
43        GenericApplicationId, ModuleId, StreamId, StreamName,
44    },
45    ownership::ChainOwnership,
46    vm::VmRuntime,
47};
48use linera_views::{batch::Batch, ViewError};
49use serde::{Deserialize, Serialize};
50use system::AdminOperation;
51use thiserror::Error;
52pub use web_thread_pool::Pool as ThreadPool;
53use web_thread_select as web_thread;
54
55#[cfg(with_revm)]
56use crate::evm::EvmExecutionError;
57use crate::system::EPOCH_STREAM_NAME;
58#[cfg(with_testing)]
59use crate::test_utils::dummy_chain_description;
60#[cfg(all(with_testing, with_wasm_runtime))]
61pub use crate::wasm::test as wasm_test;
62#[cfg(with_wasm_runtime)]
63pub use crate::wasm::{
64    BaseRuntimeApi, ContractEntrypoints, ContractRuntimeApi, RuntimeApiData, ServiceEntrypoints,
65    ServiceRuntimeApi, WasmContractModule, WasmExecutionError, WasmServiceModule,
66};
67pub use crate::{
68    committee::{Committee, SharedCommittees},
69    execution::{ExecutionStateView, ServiceRuntimeEndpoint},
70    execution_state_actor::{ExecutionRequest, ExecutionStateActor},
71    policy::ResourceControlPolicy,
72    resources::{BalanceHolder, ResourceController, ResourceTracker},
73    runtime::{
74        ContractSyncRuntimeHandle, ServiceRuntimeRequest, ServiceSyncRuntime,
75        ServiceSyncRuntimeHandle,
76    },
77    system::{
78        SystemExecutionStateView, SystemMessage, SystemOperation, SystemQuery, SystemResponse,
79    },
80    transaction_tracker::{TransactionOutcome, TransactionTracker},
81};
82
83/// The `Linera.sol` library code to be included in solidity smart
84/// contracts using Linera features.
85pub const LINERA_SOL: &str = include_str!("../solidity/Linera.sol");
86pub const LINERA_TYPES_SOL: &str = include_str!("../solidity/LineraTypes.sol");
87
88/// The maximum length of a stream name.
89const MAX_STREAM_NAME_LEN: usize = 64;
90
91/// The flag that, if present in `http_request_allow_list` field of the content policy of
92/// current committee, causes the execution state not to be hashed, and instead the hash
93/// returned to be all zeros.
94// Note: testnet-only! This should not survive to mainnet.
95pub const FLAG_ZERO_HASH: &str = "FLAG_ZERO_HASH.linera.network";
96/// The flag that deactivates charging for bouncing messages. If this is present, outgoing
97/// messages are free of charge if they are bouncing, and operation outcomes are counted only
98/// by payload size, so that rejecting messages is free.
99pub const FLAG_FREE_REJECT: &str = "FLAG_FREE_REJECT.linera.network";
100/// The flag that makes mandatory application checks require accepted messages. If this is
101/// present, only accepted incoming messages (not rejected ones) satisfy the mandatory
102/// applications requirement for a block.
103pub const FLAG_MANDATORY_APPS_NEED_ACCEPTED_MESSAGE: &str =
104    "FLAG_MANDATORY_APPS_NEED_ACCEPTED_MESSAGE.linera.network";
105/// The prefix for flags that mark an application as free (message- and event-related fees waived).
106/// The full flag is `FLAG_FREE_APPLICATION_ID_<APP_ID>.linera.network`.
107pub const FLAG_FREE_APPLICATION_ID_PREFIX: &str = "FLAG_FREE_APPLICATION_ID_";
108/// The suffix for free application ID flags.
109pub const FLAG_FREE_APPLICATION_ID_SUFFIX: &str = ".linera.network";
110
111/// An implementation of [`UserContractModule`].
112#[derive(Clone)]
113pub struct UserContractCode(Box<dyn UserContractModule>);
114
115/// An implementation of [`UserServiceModule`].
116#[derive(Clone)]
117pub struct UserServiceCode(Box<dyn UserServiceModule>);
118
119/// An implementation of [`UserContract`].
120pub type UserContractInstance = Box<dyn UserContract>;
121
122/// An implementation of [`UserService`].
123pub type UserServiceInstance = Box<dyn UserService>;
124
125/// A factory trait to obtain a [`UserContract`] from a [`UserContractModule`]
126pub trait UserContractModule: dyn_clone::DynClone + Any + web_thread::Post + Send + Sync {
127    fn instantiate(
128        &self,
129        runtime: ContractSyncRuntimeHandle,
130    ) -> Result<UserContractInstance, ExecutionError>;
131}
132
133impl<T: UserContractModule + Send + Sync + 'static> From<T> for UserContractCode {
134    fn from(module: T) -> Self {
135        Self(Box::new(module))
136    }
137}
138
139dyn_clone::clone_trait_object!(UserContractModule);
140
141/// A factory trait to obtain a [`UserService`] from a [`UserServiceModule`]
142pub trait UserServiceModule: dyn_clone::DynClone + Any + web_thread::Post + Send + Sync {
143    fn instantiate(
144        &self,
145        runtime: ServiceSyncRuntimeHandle,
146    ) -> Result<UserServiceInstance, ExecutionError>;
147}
148
149impl<T: UserServiceModule + Send + Sync + 'static> From<T> for UserServiceCode {
150    fn from(module: T) -> Self {
151        Self(Box::new(module))
152    }
153}
154
155dyn_clone::clone_trait_object!(UserServiceModule);
156
157impl UserServiceCode {
158    fn instantiate(
159        &self,
160        runtime: ServiceSyncRuntimeHandle,
161    ) -> Result<UserServiceInstance, ExecutionError> {
162        self.0.instantiate(runtime)
163    }
164}
165
166impl UserContractCode {
167    fn instantiate(
168        &self,
169        runtime: ContractSyncRuntimeHandle,
170    ) -> Result<UserContractInstance, ExecutionError> {
171        self.0.instantiate(runtime)
172    }
173}
174
175pub struct JsVec<T>(pub Vec<T>);
176
177#[cfg(web)]
178const _: () = {
179    // TODO(#2775): add a vtable pointer into the JsValue rather than assuming the
180    // implementor
181
182    impl web_thread::AsJs for UserContractCode {
183        fn to_js(&self) -> Result<JsValue, JsValue> {
184            ((&*self.0) as &dyn Any)
185                .downcast_ref::<WasmContractModule>()
186                .expect("we only support Wasm modules on the Web for now")
187                .to_js()
188        }
189
190        fn from_js(value: JsValue) -> Result<Self, JsValue> {
191            WasmContractModule::from_js(value).map(Into::into)
192        }
193    }
194
195    impl web_thread::Post for UserContractCode {
196        fn transferables(&self) -> js_sys::Array {
197            self.0.transferables()
198        }
199    }
200
201    impl web_thread::AsJs for UserServiceCode {
202        fn to_js(&self) -> Result<JsValue, JsValue> {
203            ((&*self.0) as &dyn Any)
204                .downcast_ref::<WasmServiceModule>()
205                .expect("we only support Wasm modules on the Web for now")
206                .to_js()
207        }
208
209        fn from_js(value: JsValue) -> Result<Self, JsValue> {
210            WasmServiceModule::from_js(value).map(Into::into)
211        }
212    }
213
214    impl web_thread::Post for UserServiceCode {
215        fn transferables(&self) -> js_sys::Array {
216            self.0.transferables()
217        }
218    }
219
220    impl<T: web_thread::AsJs> web_thread::AsJs for JsVec<T> {
221        fn to_js(&self) -> Result<JsValue, JsValue> {
222            let array = self
223                .0
224                .iter()
225                .map(T::to_js)
226                .collect::<Result<js_sys::Array, _>>()?;
227            Ok(array.into())
228        }
229
230        fn from_js(value: JsValue) -> Result<Self, JsValue> {
231            let array = js_sys::Array::from(&value);
232            let v = array
233                .into_iter()
234                .map(T::from_js)
235                .collect::<Result<Vec<_>, _>>()?;
236            Ok(JsVec(v))
237        }
238    }
239
240    impl<T: web_thread::Post> web_thread::Post for JsVec<T> {
241        fn transferables(&self) -> js_sys::Array {
242            let mut array = js_sys::Array::new();
243            for x in &self.0 {
244                array = array.concat(&x.transferables());
245            }
246            array
247        }
248    }
249};
250
251/// A type for errors happening during execution.
252#[derive(Error, Debug, strum::IntoStaticStr)]
253pub enum ExecutionError {
254    #[error(transparent)]
255    ViewError(#[from] ViewError),
256    #[error(transparent)]
257    ArithmeticError(#[from] ArithmeticError),
258    #[error("User application reported an error: {0}")]
259    UserError(String),
260    #[cfg(with_wasm_runtime)]
261    #[error(transparent)]
262    WasmError(#[from] WasmExecutionError),
263    #[cfg(with_revm)]
264    #[error(transparent)]
265    EvmError(#[from] EvmExecutionError),
266    #[error(transparent)]
267    DecompressionError(#[from] DecompressionError),
268    #[error("The given promise is invalid or was polled once already")]
269    InvalidPromise,
270
271    #[error("Attempted to perform a reentrant call to application {0}")]
272    ReentrantCall(ApplicationId),
273    #[error(
274        "Application {caller_id} attempted to perform a cross-application to {callee_id} call \
275        from `finalize`"
276    )]
277    CrossApplicationCallInFinalize {
278        caller_id: Box<ApplicationId>,
279        callee_id: Box<ApplicationId>,
280    },
281    #[error("Failed to load bytecode from storage {0:?}")]
282    ApplicationBytecodeNotFound(Box<ApplicationDescription>),
283    // TODO(#2927): support dynamic loading of modules on the Web
284    #[error("Unsupported dynamic application load: {0:?}")]
285    UnsupportedDynamicApplicationLoad(Box<ApplicationId>),
286
287    #[error("Excessive number of bytes read from storage")]
288    ExcessiveRead,
289    #[error("Excessive number of bytes written to storage")]
290    ExcessiveWrite,
291    #[error("Block execution required too much fuel for VM {0}")]
292    MaximumFuelExceeded(VmRuntime),
293    #[error("Services running as oracles in block took longer than allowed")]
294    MaximumServiceOracleExecutionTimeExceeded,
295    #[error("Service running as an oracle produced a response that's too large")]
296    ServiceOracleResponseTooLarge,
297    #[error("Serialized size of the block exceeds limit")]
298    BlockTooLarge,
299    #[error("HTTP response exceeds the size limit of {limit} bytes, having at least {size} bytes")]
300    HttpResponseSizeLimitExceeded { limit: u64, size: u64 },
301    #[error("Runtime failed to respond to application")]
302    MissingRuntimeResponse,
303    #[error("Application is not authorized to perform system operations on this chain: {0:}")]
304    UnauthorizedApplication(ApplicationId),
305    #[error("Failed to make network reqwest: {0}")]
306    ReqwestError(#[from] reqwest::Error),
307    #[error("Encountered I/O error: {0}")]
308    IoError(#[from] std::io::Error),
309    #[error("More recorded oracle responses than expected")]
310    UnexpectedOracleResponse,
311    #[error("Invalid JSON: {0}")]
312    JsonError(#[from] serde_json::Error),
313    #[error(transparent)]
314    BcsError(#[from] bcs::Error),
315    #[error("Recorded response for oracle query has the wrong type")]
316    OracleResponseMismatch,
317    #[error("Service oracle query tried to create operations: {0:?}")]
318    ServiceOracleQueryOperations(Vec<Operation>),
319    #[error("Assertion failed: local time {local_time} is not earlier than {timestamp}")]
320    AssertBefore {
321        timestamp: Timestamp,
322        local_time: Timestamp,
323    },
324
325    #[error("Stream names can be at most {MAX_STREAM_NAME_LEN} bytes.")]
326    StreamNameTooLong,
327    #[error("Blob exceeds size limit")]
328    BlobTooLarge,
329    #[error("Bytecode exceeds size limit")]
330    BytecodeTooLarge,
331    #[error("Attempt to perform an HTTP request to an unauthorized host: {0:?}")]
332    UnauthorizedHttpRequest(reqwest::Url),
333    #[error("Attempt to perform an HTTP request to an invalid URL")]
334    InvalidUrlForHttpRequest(#[from] url::ParseError),
335    #[error("Worker thread failure: {0:?}")]
336    Thread(#[from] web_thread::Error),
337    #[error("Blobs not found: {0:?}")]
338    BlobsNotFound(Vec<BlobId>),
339    #[error("Events not found: {0:?}")]
340    EventsNotFound(Vec<EventId>),
341
342    #[error("Invalid HTTP header name used for HTTP request")]
343    InvalidHeaderName(#[from] reqwest::header::InvalidHeaderName),
344    #[error("Invalid HTTP header value used for HTTP request")]
345    InvalidHeaderValue(#[from] reqwest::header::InvalidHeaderValue),
346
347    #[error("No NetworkDescription found in storage")]
348    NoNetworkDescriptionFound,
349    #[error("{epoch:?} is not recognized by chain {chain_id:}")]
350    InvalidEpoch { chain_id: ChainId, epoch: Epoch },
351    #[error("Transfer must have positive amount")]
352    IncorrectTransferAmount,
353    #[error("Transfer from owned account must be authenticated by the right signer")]
354    UnauthenticatedTransferOwner,
355    #[error("The transferred amount must not exceed the balance of the current account {account}: {balance}")]
356    InsufficientBalance {
357        balance: Amount,
358        account: AccountOwner,
359    },
360    #[error("Required execution fees exceeded the total funding available. Fees {fees}, available balance: {balance}")]
361    FeesExceedFunding { fees: Amount, balance: Amount },
362    #[error("Claim must have positive amount")]
363    IncorrectClaimAmount,
364    #[error("Claim must be authenticated by the right signer")]
365    UnauthenticatedClaimOwner,
366    #[error("Admin operations are only allowed on the admin chain.")]
367    AdminOperationOnNonAdminChain,
368    #[error("Failed to create new committee: expected {expected}, but got {provided}")]
369    InvalidCommitteeEpoch { expected: Epoch, provided: Epoch },
370    #[error("Failed to remove committee")]
371    InvalidCommitteeRemoval,
372    #[error("No recorded response for oracle query")]
373    MissingOracleResponse,
374    #[error("process_streams was not called for all stream updates")]
375    UnprocessedStreams,
376    #[error("Internal error: {0}")]
377    InternalError(&'static str),
378    #[error("UpdateStreams is outdated")]
379    OutdatedUpdateStreams,
380}
381
382impl ExecutionError {
383    /// Returns whether this error is caused by an issue in the local node.
384    ///
385    /// Returns `false` whenever the error could be caused by a bad message from a peer.
386    pub fn is_local(&self) -> bool {
387        match self {
388            ExecutionError::ArithmeticError(_)
389            | ExecutionError::UserError(_)
390            | ExecutionError::DecompressionError(_)
391            | ExecutionError::InvalidPromise
392            | ExecutionError::CrossApplicationCallInFinalize { .. }
393            | ExecutionError::ReentrantCall(_)
394            | ExecutionError::ApplicationBytecodeNotFound(_)
395            | ExecutionError::UnsupportedDynamicApplicationLoad(_)
396            | ExecutionError::ExcessiveRead
397            | ExecutionError::ExcessiveWrite
398            | ExecutionError::MaximumFuelExceeded(_)
399            | ExecutionError::MaximumServiceOracleExecutionTimeExceeded
400            | ExecutionError::ServiceOracleResponseTooLarge
401            | ExecutionError::BlockTooLarge
402            | ExecutionError::HttpResponseSizeLimitExceeded { .. }
403            | ExecutionError::UnauthorizedApplication(_)
404            | ExecutionError::UnexpectedOracleResponse
405            | ExecutionError::JsonError(_)
406            | ExecutionError::BcsError(_)
407            | ExecutionError::OracleResponseMismatch
408            | ExecutionError::ServiceOracleQueryOperations(_)
409            | ExecutionError::AssertBefore { .. }
410            | ExecutionError::StreamNameTooLong
411            | ExecutionError::BlobTooLarge
412            | ExecutionError::BytecodeTooLarge
413            | ExecutionError::UnauthorizedHttpRequest(_)
414            | ExecutionError::InvalidUrlForHttpRequest(_)
415            | ExecutionError::BlobsNotFound(_)
416            | ExecutionError::EventsNotFound(_)
417            | ExecutionError::InvalidHeaderName(_)
418            | ExecutionError::InvalidHeaderValue(_)
419            | ExecutionError::InvalidEpoch { .. }
420            | ExecutionError::IncorrectTransferAmount
421            | ExecutionError::UnauthenticatedTransferOwner
422            | ExecutionError::InsufficientBalance { .. }
423            | ExecutionError::FeesExceedFunding { .. }
424            | ExecutionError::IncorrectClaimAmount
425            | ExecutionError::UnauthenticatedClaimOwner
426            | ExecutionError::AdminOperationOnNonAdminChain
427            | ExecutionError::InvalidCommitteeEpoch { .. }
428            | ExecutionError::InvalidCommitteeRemoval
429            | ExecutionError::MissingOracleResponse
430            | ExecutionError::UnprocessedStreams
431            | ExecutionError::OutdatedUpdateStreams
432            | ExecutionError::ViewError(ViewError::NotFound(_)) => false,
433            #[cfg(with_wasm_runtime)]
434            ExecutionError::WasmError(_) => false,
435            #[cfg(with_revm)]
436            ExecutionError::EvmError(..) => false,
437            ExecutionError::MissingRuntimeResponse
438            | ExecutionError::ViewError(_)
439            | ExecutionError::ReqwestError(_)
440            | ExecutionError::Thread(_)
441            | ExecutionError::NoNetworkDescriptionFound
442            | ExecutionError::InternalError(_)
443            | ExecutionError::IoError(_) => true,
444        }
445    }
446
447    /// Returns the qualified error variant name for the `error_type` metric label,
448    /// e.g. `"ExecutionError::BlobsNotFound"`.
449    pub fn error_type(&self) -> String {
450        let variant: &'static str = self.into();
451        format!("ExecutionError::{variant}")
452    }
453
454    /// Returns whether this error is caused by a per-block limit being exceeded.
455    ///
456    /// These are errors that might succeed in a later block if the limit was only exceeded
457    /// due to accumulated transactions. Per-transaction or per-call limits are not included.
458    pub fn is_limit_error(&self) -> bool {
459        matches!(
460            self,
461            ExecutionError::ExcessiveRead
462                | ExecutionError::ExcessiveWrite
463                | ExecutionError::MaximumFuelExceeded(_)
464                | ExecutionError::MaximumServiceOracleExecutionTimeExceeded
465                | ExecutionError::BlockTooLarge
466        )
467    }
468
469    /// Returns whether this is a transient error that may resolve after syncing.
470    ///
471    /// Transient errors like missing blobs or events might succeed after the node syncs
472    /// with the network. These errors should fail the block entirely (not reject the message)
473    /// so the block can be retried later.
474    pub fn is_transient_error(&self) -> bool {
475        matches!(
476            self,
477            ExecutionError::BlobsNotFound(_) | ExecutionError::EventsNotFound(_)
478        )
479    }
480}
481
482/// The public entry points provided by the contract part of an application.
483pub trait UserContract {
484    /// Instantiate the application state on the chain that owns the application.
485    fn instantiate(&mut self, argument: Vec<u8>) -> Result<(), ExecutionError>;
486
487    /// Applies an operation from the current block.
488    fn execute_operation(&mut self, operation: Vec<u8>) -> Result<Vec<u8>, ExecutionError>;
489
490    /// Applies a message originating from a cross-chain message.
491    fn execute_message(&mut self, message: Vec<u8>) -> Result<(), ExecutionError>;
492
493    /// Reacts to new events on streams this application subscribes to.
494    fn process_streams(&mut self, updates: Vec<StreamUpdate>) -> Result<(), ExecutionError>;
495
496    /// Finishes execution of the current transaction.
497    fn finalize(&mut self) -> Result<(), ExecutionError>;
498}
499
500/// The public entry points provided by the service part of an application.
501pub trait UserService {
502    /// Executes unmetered read-only queries on the state of this application.
503    fn handle_query(&mut self, argument: Vec<u8>) -> Result<Vec<u8>, ExecutionError>;
504}
505
506/// Configuration options for the execution runtime available to applications.
507#[derive(Clone, Copy)]
508pub struct ExecutionRuntimeConfig {
509    /// Whether contract log messages should be output.
510    /// This is typically enabled for clients but disabled for validators.
511    pub allow_application_logs: bool,
512}
513
514impl Default for ExecutionRuntimeConfig {
515    fn default() -> Self {
516        Self {
517            allow_application_logs: true,
518        }
519    }
520}
521
522/// Requirements for the `extra` field in our state views (and notably the
523/// [`ExecutionStateView`]).
524#[cfg_attr(not(web), async_trait)]
525#[cfg_attr(web, async_trait(?Send))]
526pub trait ExecutionRuntimeContext {
527    fn chain_id(&self) -> ChainId;
528
529    fn thread_pool(&self) -> &Arc<ThreadPool>;
530
531    fn execution_runtime_config(&self) -> ExecutionRuntimeConfig;
532
533    fn user_contracts(&self) -> &Arc<papaya::HashMap<ApplicationId, UserContractCode>>;
534
535    fn user_services(&self) -> &Arc<papaya::HashMap<ApplicationId, UserServiceCode>>;
536
537    async fn get_user_contract(
538        &self,
539        description: &ApplicationDescription,
540        txn_tracker: &TransactionTracker,
541    ) -> Result<UserContractCode, ExecutionError>;
542
543    async fn get_user_service(
544        &self,
545        description: &ApplicationDescription,
546        txn_tracker: &TransactionTracker,
547    ) -> Result<UserServiceCode, ExecutionError>;
548
549    async fn get_blob(&self, blob_id: BlobId) -> Result<Option<Arc<Blob>>, ViewError>;
550
551    async fn get_event(&self, event_id: EventId) -> Result<Option<Arc<Vec<u8>>>, ViewError>;
552
553    async fn get_network_description(&self) -> Result<Option<NetworkDescription>, ViewError>;
554
555    /// Returns the committees for the epochs in the given range. Delegates per-epoch
556    /// look-ups to [`Self::get_or_load_committee`] so the process-global cache is hit,
557    /// and surfaces any missing epochs as a single [`ExecutionError::EventsNotFound`].
558    async fn get_committees(
559        &self,
560        epoch_range: RangeInclusive<Epoch>,
561    ) -> Result<BTreeMap<Epoch, Committee>, ExecutionError> {
562        let mut committees = BTreeMap::new();
563        let mut missing = Vec::new();
564        for index in epoch_range.start().0..=epoch_range.end().0 {
565            let epoch = Epoch(index);
566            match self.get_or_load_committee(epoch).await? {
567                Some(committee) => {
568                    committees.insert(epoch, (*committee).clone());
569                }
570                None => missing.push(epoch),
571            }
572        }
573        if !missing.is_empty() {
574            let net_description = self
575                .get_network_description()
576                .await?
577                .ok_or(ExecutionError::NoNetworkDescriptionFound)?;
578            let event_ids = missing
579                .into_iter()
580                .map(|epoch| EventId {
581                    chain_id: net_description.admin_chain_id,
582                    stream_id: StreamId::system(EPOCH_STREAM_NAME),
583                    index: epoch.0,
584                })
585                .collect();
586            return Err(ExecutionError::EventsNotFound(event_ids));
587        }
588        Ok(committees)
589    }
590
591    /// Returns the committee for `epoch`, consulting the shared cache first. On a miss,
592    /// loads the `NewCommittee` event and the committee blob from storage and memoizes
593    /// the result. Returns `Ok(None)` if the network description, the event, or the
594    /// blob is not available locally.
595    ///
596    /// Determinism during block execution: call sites inside the runtime must only ask
597    /// for epochs up to and including the chain's current epoch (`self.epoch.get()`).
598    /// The chain-state invariant guarantees that every such committee is knowable (either
599    /// in the shared cache, in storage, or — for the chain's current epoch during a block
600    /// that just created it — in the pending update on the chain's own `committees`
601    /// view). Queries for strictly greater epochs read from a mutable process-wide cache
602    /// and are not deterministic.
603    async fn get_or_load_committee(
604        &self,
605        epoch: Epoch,
606    ) -> Result<Option<Arc<Committee>>, ViewError>;
607
608    async fn contains_blob(&self, blob_id: BlobId) -> Result<bool, ViewError>;
609
610    async fn contains_event(&self, event_id: EventId) -> Result<bool, ViewError>;
611
612    #[cfg(with_testing)]
613    async fn add_blobs(
614        &self,
615        blobs: impl IntoIterator<Item = Blob> + Send,
616    ) -> Result<(), ViewError>;
617
618    #[cfg(with_testing)]
619    async fn add_events(
620        &self,
621        events: impl IntoIterator<Item = (EventId, Vec<u8>)> + Send,
622    ) -> Result<(), ViewError>;
623}
624
625#[derive(Clone, Copy, Debug)]
626pub struct OperationContext {
627    /// The current chain ID.
628    pub chain_id: ChainId,
629    /// The authenticated signer of the operation, if any.
630    #[debug(skip_if = Option::is_none)]
631    pub authenticated_signer: Option<AccountOwner>,
632    /// The current block height.
633    pub height: BlockHeight,
634    /// The consensus round number, if this is a block that gets validated in a multi-leader round.
635    pub round: Option<u32>,
636    /// The timestamp of the block containing the operation.
637    pub timestamp: Timestamp,
638}
639
640#[derive(Clone, Copy, Debug)]
641pub struct MessageContext {
642    /// The current chain ID.
643    pub chain_id: ChainId,
644    /// The chain ID where the message originated from.
645    pub origin: ChainId,
646    /// Whether the message was rejected by the original receiver and is now bouncing back.
647    pub is_bouncing: bool,
648    /// The authenticated signer of the operation that created the message, if any.
649    #[debug(skip_if = Option::is_none)]
650    pub authenticated_signer: Option<AccountOwner>,
651    /// Where to send a refund for the unused part of each grant after execution, if any.
652    #[debug(skip_if = Option::is_none)]
653    pub refund_grant_to: Option<Account>,
654    /// The current block height.
655    pub height: BlockHeight,
656    /// The consensus round number, if this is a block that gets validated in a multi-leader round.
657    pub round: Option<u32>,
658    /// The timestamp of the block executing the message.
659    pub timestamp: Timestamp,
660}
661
662#[derive(Clone, Copy, Debug)]
663pub struct ProcessStreamsContext {
664    /// The current chain ID.
665    pub chain_id: ChainId,
666    /// The current block height.
667    pub height: BlockHeight,
668    /// The consensus round number, if this is a block that gets validated in a multi-leader round.
669    pub round: Option<u32>,
670    /// The timestamp of the current block.
671    pub timestamp: Timestamp,
672}
673
674impl From<MessageContext> for ProcessStreamsContext {
675    fn from(context: MessageContext) -> Self {
676        Self {
677            chain_id: context.chain_id,
678            height: context.height,
679            round: context.round,
680            timestamp: context.timestamp,
681        }
682    }
683}
684
685impl From<OperationContext> for ProcessStreamsContext {
686    fn from(context: OperationContext) -> Self {
687        Self {
688            chain_id: context.chain_id,
689            height: context.height,
690            round: context.round,
691            timestamp: context.timestamp,
692        }
693    }
694}
695
696#[derive(Clone, Copy, Debug)]
697pub struct FinalizeContext {
698    /// The current chain ID.
699    pub chain_id: ChainId,
700    /// The authenticated signer of the operation, if any.
701    #[debug(skip_if = Option::is_none)]
702    pub authenticated_signer: Option<AccountOwner>,
703    /// The current block height.
704    pub height: BlockHeight,
705    /// The consensus round number, if this is a block that gets validated in a multi-leader round.
706    pub round: Option<u32>,
707}
708
709#[derive(Clone, Copy, Debug, Eq, PartialEq)]
710pub struct QueryContext {
711    /// The current chain ID.
712    pub chain_id: ChainId,
713    /// The height of the next block on this chain.
714    pub next_block_height: BlockHeight,
715    /// The local time in the node executing the query.
716    pub local_time: Timestamp,
717}
718
719pub trait BaseRuntime {
720    type Read: fmt::Debug + Send + Sync;
721    type ContainsKey: fmt::Debug + Send + Sync;
722    type ContainsKeys: fmt::Debug + Send + Sync;
723    type ReadMultiValuesBytes: fmt::Debug + Send + Sync;
724    type ReadValueBytes: fmt::Debug + Send + Sync;
725    type FindKeysByPrefix: fmt::Debug + Send + Sync;
726    type FindKeyValuesByPrefix: fmt::Debug + Send + Sync;
727
728    /// The current chain ID.
729    fn chain_id(&mut self) -> Result<ChainId, ExecutionError>;
730
731    /// The current block height.
732    fn block_height(&mut self) -> Result<BlockHeight, ExecutionError>;
733
734    /// The current application ID.
735    fn application_id(&mut self) -> Result<ApplicationId, ExecutionError>;
736
737    /// The current application creator's chain ID.
738    fn application_creator_chain_id(&mut self) -> Result<ChainId, ExecutionError>;
739
740    /// Returns the description of the given application.
741    fn read_application_description(
742        &mut self,
743        application_id: ApplicationId,
744    ) -> Result<ApplicationDescription, ExecutionError>;
745
746    /// The current application parameters.
747    fn application_parameters(&mut self) -> Result<Vec<u8>, ExecutionError>;
748
749    /// Reads the system timestamp.
750    fn read_system_timestamp(&mut self) -> Result<Timestamp, ExecutionError>;
751
752    /// Reads the balance of the chain.
753    fn read_chain_balance(&mut self) -> Result<Amount, ExecutionError>;
754
755    /// Reads the owner balance.
756    fn read_owner_balance(&mut self, owner: AccountOwner) -> Result<Amount, ExecutionError>;
757
758    /// Reads the balances from all owners.
759    fn read_owner_balances(&mut self) -> Result<Vec<(AccountOwner, Amount)>, ExecutionError>;
760
761    /// Reads balance owners.
762    fn read_balance_owners(&mut self) -> Result<Vec<AccountOwner>, ExecutionError>;
763
764    /// Reads the current ownership configuration for this chain.
765    fn chain_ownership(&mut self) -> Result<ChainOwnership, ExecutionError>;
766
767    /// Reads the current application permissions for this chain.
768    fn application_permissions(&mut self) -> Result<ApplicationPermissions, ExecutionError>;
769
770    /// Tests whether a key exists in the key-value store
771    #[cfg(feature = "test")]
772    fn contains_key(&mut self, key: Vec<u8>) -> Result<bool, ExecutionError> {
773        let promise = self.contains_key_new(key)?;
774        self.contains_key_wait(&promise)
775    }
776
777    /// Creates the promise to test whether a key exists in the key-value store
778    fn contains_key_new(&mut self, key: Vec<u8>) -> Result<Self::ContainsKey, ExecutionError>;
779
780    /// Resolves the promise to test whether a key exists in the key-value store
781    fn contains_key_wait(&mut self, promise: &Self::ContainsKey) -> Result<bool, ExecutionError>;
782
783    /// Tests whether multiple keys exist in the key-value store
784    #[cfg(feature = "test")]
785    fn contains_keys(&mut self, keys: Vec<Vec<u8>>) -> Result<Vec<bool>, ExecutionError> {
786        let promise = self.contains_keys_new(keys)?;
787        self.contains_keys_wait(&promise)
788    }
789
790    /// Creates the promise to test whether multiple keys exist in the key-value store
791    fn contains_keys_new(
792        &mut self,
793        keys: Vec<Vec<u8>>,
794    ) -> Result<Self::ContainsKeys, ExecutionError>;
795
796    /// Resolves the promise to test whether multiple keys exist in the key-value store
797    fn contains_keys_wait(
798        &mut self,
799        promise: &Self::ContainsKeys,
800    ) -> Result<Vec<bool>, ExecutionError>;
801
802    /// Reads several keys from the key-value store
803    #[cfg(feature = "test")]
804    fn read_multi_values_bytes(
805        &mut self,
806        keys: Vec<Vec<u8>>,
807    ) -> Result<Vec<Option<Vec<u8>>>, ExecutionError> {
808        let promise = self.read_multi_values_bytes_new(keys)?;
809        self.read_multi_values_bytes_wait(&promise)
810    }
811
812    /// Creates the promise to access several keys from the key-value store
813    fn read_multi_values_bytes_new(
814        &mut self,
815        keys: Vec<Vec<u8>>,
816    ) -> Result<Self::ReadMultiValuesBytes, ExecutionError>;
817
818    /// Resolves the promise to access several keys from the key-value store
819    fn read_multi_values_bytes_wait(
820        &mut self,
821        promise: &Self::ReadMultiValuesBytes,
822    ) -> Result<Vec<Option<Vec<u8>>>, ExecutionError>;
823
824    /// Reads the key from the key-value store
825    #[cfg(feature = "test")]
826    fn read_value_bytes(&mut self, key: Vec<u8>) -> Result<Option<Vec<u8>>, ExecutionError> {
827        let promise = self.read_value_bytes_new(key)?;
828        self.read_value_bytes_wait(&promise)
829    }
830
831    /// Creates the promise to access a key from the key-value store
832    fn read_value_bytes_new(
833        &mut self,
834        key: Vec<u8>,
835    ) -> Result<Self::ReadValueBytes, ExecutionError>;
836
837    /// Resolves the promise to access a key from the key-value store
838    fn read_value_bytes_wait(
839        &mut self,
840        promise: &Self::ReadValueBytes,
841    ) -> Result<Option<Vec<u8>>, ExecutionError>;
842
843    /// Creates the promise to access keys having a specific prefix
844    fn find_keys_by_prefix_new(
845        &mut self,
846        key_prefix: Vec<u8>,
847    ) -> Result<Self::FindKeysByPrefix, ExecutionError>;
848
849    /// Resolves the promise to access keys having a specific prefix
850    fn find_keys_by_prefix_wait(
851        &mut self,
852        promise: &Self::FindKeysByPrefix,
853    ) -> Result<Vec<Vec<u8>>, ExecutionError>;
854
855    /// Reads the data from the key/values having a specific prefix.
856    #[cfg(feature = "test")]
857    #[expect(clippy::type_complexity)]
858    fn find_key_values_by_prefix(
859        &mut self,
860        key_prefix: Vec<u8>,
861    ) -> Result<Vec<(Vec<u8>, Vec<u8>)>, ExecutionError> {
862        let promise = self.find_key_values_by_prefix_new(key_prefix)?;
863        self.find_key_values_by_prefix_wait(&promise)
864    }
865
866    /// Creates the promise to access key/values having a specific prefix
867    fn find_key_values_by_prefix_new(
868        &mut self,
869        key_prefix: Vec<u8>,
870    ) -> Result<Self::FindKeyValuesByPrefix, ExecutionError>;
871
872    /// Resolves the promise to access key/values having a specific prefix
873    #[expect(clippy::type_complexity)]
874    fn find_key_values_by_prefix_wait(
875        &mut self,
876        promise: &Self::FindKeyValuesByPrefix,
877    ) -> Result<Vec<(Vec<u8>, Vec<u8>)>, ExecutionError>;
878
879    /// Makes an HTTP request to the given URL and returns the answer, if any.
880    fn perform_http_request(
881        &mut self,
882        request: http::Request,
883    ) -> Result<http::Response, ExecutionError>;
884
885    /// Ensures that the current time at block validation is `< timestamp`. Note that block
886    /// validation happens at or after the block timestamp, but isn't necessarily the same.
887    ///
888    /// Cannot be used in fast blocks: A block using this call should be proposed by a regular
889    /// owner, not a super owner.
890    fn assert_before(&mut self, timestamp: Timestamp) -> Result<(), ExecutionError>;
891
892    /// Reads a data blob specified by a given hash.
893    fn read_data_blob(&mut self, hash: DataBlobHash) -> Result<Vec<u8>, ExecutionError>;
894
895    /// Asserts the existence of a data blob with the given hash.
896    fn assert_data_blob_exists(&mut self, hash: DataBlobHash) -> Result<(), ExecutionError>;
897
898    /// Returns whether contract log messages should be output.
899    /// This is typically enabled for clients but disabled for validators.
900    fn allow_application_logs(&mut self) -> Result<bool, ExecutionError>;
901
902    /// Sends a log message (used for forwarding logs from web workers to the main thread).
903    /// This is a fire-and-forget operation - errors are silently ignored.
904    #[cfg(web)]
905    fn send_log(&mut self, message: String, level: tracing::log::Level);
906}
907
908pub trait ServiceRuntime: BaseRuntime {
909    /// Queries another application.
910    fn try_query_application(
911        &mut self,
912        queried_id: ApplicationId,
913        argument: Vec<u8>,
914    ) -> Result<Vec<u8>, ExecutionError>;
915
916    /// Schedules an operation to be included in the block proposed after execution.
917    fn schedule_operation(&mut self, operation: Vec<u8>) -> Result<(), ExecutionError>;
918
919    /// Checks if the service has exceeded its execution time limit.
920    fn check_execution_time(&mut self) -> Result<(), ExecutionError>;
921}
922
923pub trait ContractRuntime: BaseRuntime {
924    /// The authenticated signer for this execution, if there is one.
925    fn authenticated_signer(&mut self) -> Result<Option<AccountOwner>, ExecutionError>;
926
927    /// If the current message (if there is one) was rejected by its destination and is now
928    /// bouncing back.
929    fn message_is_bouncing(&mut self) -> Result<Option<bool>, ExecutionError>;
930
931    /// The chain ID where the current message originated from, if there is one.
932    fn message_origin_chain_id(&mut self) -> Result<Option<ChainId>, ExecutionError>;
933
934    /// The optional authenticated caller application ID, if it was provided and if there is one
935    /// based on the execution context.
936    fn authenticated_caller_id(&mut self) -> Result<Option<ApplicationId>, ExecutionError>;
937
938    /// Returns the maximum gas fuel per block.
939    fn maximum_fuel_per_block(&mut self, vm_runtime: VmRuntime) -> Result<u64, ExecutionError>;
940
941    /// Returns the amount of execution fuel remaining before execution is aborted.
942    fn remaining_fuel(&mut self, vm_runtime: VmRuntime) -> Result<u64, ExecutionError>;
943
944    /// Consumes some of the execution fuel.
945    fn consume_fuel(&mut self, fuel: u64, vm_runtime: VmRuntime) -> Result<(), ExecutionError>;
946
947    /// Schedules a message to be sent.
948    fn send_message(&mut self, message: SendMessageRequest<Vec<u8>>) -> Result<(), ExecutionError>;
949
950    /// Transfers amount from source to destination.
951    fn transfer(
952        &mut self,
953        source: AccountOwner,
954        destination: Account,
955        amount: Amount,
956    ) -> Result<(), ExecutionError>;
957
958    /// Claims amount from source to destination.
959    fn claim(
960        &mut self,
961        source: Account,
962        destination: Account,
963        amount: Amount,
964    ) -> Result<(), ExecutionError>;
965
966    /// Calls another application. Forwarded sessions will now be visible to
967    /// `callee_id` (but not to the caller any more).
968    fn try_call_application(
969        &mut self,
970        authenticated: bool,
971        callee_id: ApplicationId,
972        argument: Vec<u8>,
973    ) -> Result<Vec<u8>, ExecutionError>;
974
975    /// Adds a new item to an event stream. Returns the new event's index in the stream.
976    fn emit(&mut self, name: StreamName, value: Vec<u8>) -> Result<u32, ExecutionError>;
977
978    /// Reads an event from a stream. Returns the event's value.
979    ///
980    /// Returns an error if the event doesn't exist.
981    fn read_event(
982        &mut self,
983        chain_id: ChainId,
984        stream_name: StreamName,
985        index: u32,
986    ) -> Result<Vec<u8>, ExecutionError>;
987
988    /// Subscribes this application to an event stream.
989    fn subscribe_to_events(
990        &mut self,
991        chain_id: ChainId,
992        application_id: ApplicationId,
993        stream_name: StreamName,
994    ) -> Result<(), ExecutionError>;
995
996    /// Unsubscribes this application from an event stream.
997    fn unsubscribe_from_events(
998        &mut self,
999        chain_id: ChainId,
1000        application_id: ApplicationId,
1001        stream_name: StreamName,
1002    ) -> Result<(), ExecutionError>;
1003
1004    /// Queries a service.
1005    fn query_service(
1006        &mut self,
1007        application_id: ApplicationId,
1008        query: Vec<u8>,
1009    ) -> Result<Vec<u8>, ExecutionError>;
1010
1011    /// Opens a new chain.
1012    fn open_chain(
1013        &mut self,
1014        ownership: ChainOwnership,
1015        application_permissions: ApplicationPermissions,
1016        balance: Amount,
1017    ) -> Result<ChainId, ExecutionError>;
1018
1019    /// Closes the current chain.
1020    fn close_chain(&mut self) -> Result<(), ExecutionError>;
1021
1022    /// Changes the ownership of the current chain.
1023    fn change_ownership(&mut self, ownership: ChainOwnership) -> Result<(), ExecutionError>;
1024
1025    /// Changes the application permissions on the current chain.
1026    fn change_application_permissions(
1027        &mut self,
1028        application_permissions: ApplicationPermissions,
1029    ) -> Result<(), ExecutionError>;
1030
1031    /// Creates a new application on chain.
1032    fn create_application(
1033        &mut self,
1034        module_id: ModuleId,
1035        parameters: Vec<u8>,
1036        argument: Vec<u8>,
1037        required_application_ids: Vec<ApplicationId>,
1038    ) -> Result<ApplicationId, ExecutionError>;
1039
1040    /// Creates a new data blob and returns its hash.
1041    fn create_data_blob(&mut self, bytes: Vec<u8>) -> Result<DataBlobHash, ExecutionError>;
1042
1043    /// Publishes a module with contract and service bytecode and returns the module ID.
1044    fn publish_module(
1045        &mut self,
1046        contract: Bytecode,
1047        service: Bytecode,
1048        vm_runtime: VmRuntime,
1049    ) -> Result<ModuleId, ExecutionError>;
1050
1051    /// Returns the round in which this block was validated.
1052    fn validation_round(&mut self) -> Result<Option<u32>, ExecutionError>;
1053
1054    /// Writes a batch of changes.
1055    fn write_batch(&mut self, batch: Batch) -> Result<(), ExecutionError>;
1056}
1057
1058/// An operation to be executed in a block.
1059#[derive(
1060    Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, Allocative, strum::AsRefStr,
1061)]
1062pub enum Operation {
1063    /// A system operation.
1064    System(Box<SystemOperation>),
1065    /// A user operation (in serialized form).
1066    User {
1067        application_id: ApplicationId,
1068        #[serde(with = "serde_bytes")]
1069        #[debug(with = "hex_debug")]
1070        bytes: Vec<u8>,
1071    },
1072}
1073
1074impl BcsHashable<'_> for Operation {}
1075
1076/// A message to be sent and possibly executed in the receiver's block.
1077#[derive(
1078    Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, Allocative, strum::AsRefStr,
1079)]
1080pub enum Message {
1081    /// A system message.
1082    System(SystemMessage),
1083    /// A user message (in serialized form).
1084    User {
1085        application_id: ApplicationId,
1086        #[serde(with = "serde_bytes")]
1087        #[debug(with = "hex_debug")]
1088        bytes: Vec<u8>,
1089    },
1090}
1091
1092/// An query to be sent and possibly executed in the receiver's block.
1093#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
1094pub enum Query {
1095    /// A system query.
1096    System(SystemQuery),
1097    /// A user query (in serialized form).
1098    User {
1099        application_id: ApplicationId,
1100        #[serde(with = "serde_bytes")]
1101        #[debug(with = "hex_debug")]
1102        bytes: Vec<u8>,
1103    },
1104}
1105
1106/// The outcome of the execution of a query.
1107#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
1108pub struct QueryOutcome<Response = QueryResponse> {
1109    pub response: Response,
1110    pub operations: Vec<Operation>,
1111}
1112
1113impl From<QueryOutcome<SystemResponse>> for QueryOutcome {
1114    fn from(system_outcome: QueryOutcome<SystemResponse>) -> Self {
1115        let QueryOutcome {
1116            response,
1117            operations,
1118        } = system_outcome;
1119
1120        QueryOutcome {
1121            response: QueryResponse::System(response),
1122            operations,
1123        }
1124    }
1125}
1126
1127impl From<QueryOutcome<Vec<u8>>> for QueryOutcome {
1128    fn from(user_service_outcome: QueryOutcome<Vec<u8>>) -> Self {
1129        let QueryOutcome {
1130            response,
1131            operations,
1132        } = user_service_outcome;
1133
1134        QueryOutcome {
1135            response: QueryResponse::User(response),
1136            operations,
1137        }
1138    }
1139}
1140
1141/// The response to a query.
1142#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
1143pub enum QueryResponse {
1144    /// A system response.
1145    System(SystemResponse),
1146    /// A user response (in serialized form).
1147    User(
1148        #[serde(with = "serde_bytes")]
1149        #[debug(with = "hex_debug")]
1150        Vec<u8>,
1151    ),
1152}
1153
1154/// The kind of outgoing message being sent.
1155#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, Copy, Allocative)]
1156pub enum MessageKind {
1157    /// The message can be skipped or rejected. No receipt is requested.
1158    Simple,
1159    /// The message cannot be skipped nor rejected. No receipt is requested.
1160    /// This only concerns certain system messages that cannot fail.
1161    Protected,
1162    /// The message cannot be skipped but can be rejected. A receipt must be sent
1163    /// when the message is rejected in a block of the receiver.
1164    Tracked,
1165    /// This message is a receipt automatically created when the original message was rejected.
1166    Bouncing,
1167}
1168
1169impl Display for MessageKind {
1170    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1171        match self {
1172            MessageKind::Simple => write!(f, "Simple"),
1173            MessageKind::Protected => write!(f, "Protected"),
1174            MessageKind::Tracked => write!(f, "Tracked"),
1175            MessageKind::Bouncing => write!(f, "Bouncing"),
1176        }
1177    }
1178}
1179
1180/// A posted message together with routing information.
1181#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, SimpleObject, Allocative)]
1182pub struct OutgoingMessage {
1183    /// The destination of the message.
1184    pub destination: ChainId,
1185    /// The user authentication carried by the message, if any.
1186    #[debug(skip_if = Option::is_none)]
1187    pub authenticated_signer: Option<AccountOwner>,
1188    /// A grant to pay for the message execution.
1189    #[debug(skip_if = Amount::is_zero)]
1190    pub grant: Amount,
1191    /// Where to send a refund for the unused part of the grant after execution, if any.
1192    #[debug(skip_if = Option::is_none)]
1193    pub refund_grant_to: Option<Account>,
1194    /// The kind of message being sent.
1195    pub kind: MessageKind,
1196    /// The message itself.
1197    pub message: Message,
1198}
1199
1200impl BcsHashable<'_> for OutgoingMessage {}
1201
1202impl OutgoingMessage {
1203    /// Creates a new simple outgoing message with no grant and no authenticated signer.
1204    pub fn new(recipient: ChainId, message: impl Into<Message>) -> Self {
1205        OutgoingMessage {
1206            destination: recipient,
1207            authenticated_signer: None,
1208            grant: Amount::ZERO,
1209            refund_grant_to: None,
1210            kind: MessageKind::Simple,
1211            message: message.into(),
1212        }
1213    }
1214
1215    /// Returns the same message, with the specified kind.
1216    pub fn with_kind(mut self, kind: MessageKind) -> Self {
1217        self.kind = kind;
1218        self
1219    }
1220
1221    /// Returns the same message, with the specified authenticated signer.
1222    pub fn with_authenticated_signer(mut self, authenticated_signer: Option<AccountOwner>) -> Self {
1223        self.authenticated_signer = authenticated_signer;
1224        self
1225    }
1226}
1227
1228impl OperationContext {
1229    /// Returns an account for the refund.
1230    /// Returns `None` if there is no authenticated signer of the [`OperationContext`].
1231    fn refund_grant_to(&self) -> Option<Account> {
1232        self.authenticated_signer.map(|owner| Account {
1233            chain_id: self.chain_id,
1234            owner,
1235        })
1236    }
1237}
1238
1239#[cfg(with_testing)]
1240#[derive(Clone)]
1241pub struct TestExecutionRuntimeContext {
1242    chain_id: ChainId,
1243    thread_pool: Arc<ThreadPool>,
1244    execution_runtime_config: ExecutionRuntimeConfig,
1245    user_contracts: Arc<papaya::HashMap<ApplicationId, UserContractCode>>,
1246    user_services: Arc<papaya::HashMap<ApplicationId, UserServiceCode>>,
1247    blobs: Arc<papaya::HashMap<BlobId, Blob>>,
1248    events: Arc<papaya::HashMap<EventId, Vec<u8>>>,
1249}
1250
1251#[cfg(with_testing)]
1252impl TestExecutionRuntimeContext {
1253    pub fn new(chain_id: ChainId, execution_runtime_config: ExecutionRuntimeConfig) -> Self {
1254        Self {
1255            chain_id,
1256            thread_pool: Arc::new(ThreadPool::new(20)),
1257            execution_runtime_config,
1258            user_contracts: Arc::default(),
1259            user_services: Arc::default(),
1260            blobs: Arc::default(),
1261            events: Arc::default(),
1262        }
1263    }
1264}
1265
1266#[cfg(with_testing)]
1267#[cfg_attr(not(web), async_trait)]
1268#[cfg_attr(web, async_trait(?Send))]
1269impl ExecutionRuntimeContext for TestExecutionRuntimeContext {
1270    fn chain_id(&self) -> ChainId {
1271        self.chain_id
1272    }
1273
1274    fn thread_pool(&self) -> &Arc<ThreadPool> {
1275        &self.thread_pool
1276    }
1277
1278    fn execution_runtime_config(&self) -> ExecutionRuntimeConfig {
1279        self.execution_runtime_config
1280    }
1281
1282    fn user_contracts(&self) -> &Arc<papaya::HashMap<ApplicationId, UserContractCode>> {
1283        &self.user_contracts
1284    }
1285
1286    fn user_services(&self) -> &Arc<papaya::HashMap<ApplicationId, UserServiceCode>> {
1287        &self.user_services
1288    }
1289
1290    async fn get_user_contract(
1291        &self,
1292        description: &ApplicationDescription,
1293        _txn_tracker: &TransactionTracker,
1294    ) -> Result<UserContractCode, ExecutionError> {
1295        let application_id: ApplicationId = description.into();
1296        let pinned = self.user_contracts().pin();
1297        Ok(pinned
1298            .get(&application_id)
1299            .ok_or_else(|| {
1300                ExecutionError::ApplicationBytecodeNotFound(Box::new(description.clone()))
1301            })?
1302            .clone())
1303    }
1304
1305    async fn get_user_service(
1306        &self,
1307        description: &ApplicationDescription,
1308        _txn_tracker: &TransactionTracker,
1309    ) -> Result<UserServiceCode, ExecutionError> {
1310        let application_id: ApplicationId = description.into();
1311        let pinned = self.user_services().pin();
1312        Ok(pinned
1313            .get(&application_id)
1314            .ok_or_else(|| {
1315                ExecutionError::ApplicationBytecodeNotFound(Box::new(description.clone()))
1316            })?
1317            .clone())
1318    }
1319
1320    async fn get_blob(&self, blob_id: BlobId) -> Result<Option<Arc<Blob>>, ViewError> {
1321        Ok(self.blobs.pin().get(&blob_id).cloned().map(Arc::new))
1322    }
1323
1324    async fn get_event(&self, event_id: EventId) -> Result<Option<Arc<Vec<u8>>>, ViewError> {
1325        Ok(self.events.pin().get(&event_id).cloned().map(Arc::new))
1326    }
1327
1328    async fn get_network_description(&self) -> Result<Option<NetworkDescription>, ViewError> {
1329        let pinned = self.blobs.pin();
1330        let genesis_committee_blob_hash = pinned
1331            .iter()
1332            .find(|(_, blob)| blob.content().blob_type() == BlobType::Committee)
1333            .map_or_else(
1334                || CryptoHash::test_hash("genesis committee"),
1335                |(_, blob)| blob.id().hash,
1336            );
1337        Ok(Some(NetworkDescription {
1338            admin_chain_id: dummy_chain_description(0).id(),
1339            genesis_config_hash: CryptoHash::test_hash("genesis config"),
1340            genesis_timestamp: Timestamp::from(0),
1341            genesis_committee_blob_hash,
1342            name: "dummy network description".to_string(),
1343        }))
1344    }
1345
1346    async fn get_or_load_committee(
1347        &self,
1348        epoch: Epoch,
1349    ) -> Result<Option<Arc<Committee>>, ViewError> {
1350        // No caching here — tests rarely load the same committee twice, and they don't
1351        // benefit from the process-wide deduplication that `SharedCommittees` provides
1352        // in production.
1353        let Some(net_description) = self.get_network_description().await? else {
1354            return Ok(None);
1355        };
1356        let blob_hash = if epoch.0 == 0 {
1357            net_description.genesis_committee_blob_hash
1358        } else {
1359            let event_id = EventId {
1360                chain_id: net_description.admin_chain_id,
1361                stream_id: StreamId::system(EPOCH_STREAM_NAME),
1362                index: epoch.0,
1363            };
1364            match self.get_event(event_id).await? {
1365                Some(bytes) => bcs::from_bytes(&bytes)?,
1366                None => return Ok(None),
1367            }
1368        };
1369        let blob_id = BlobId::new(blob_hash, BlobType::Committee);
1370        let Some(blob) = self.get_blob(blob_id).await? else {
1371            return Ok(None);
1372        };
1373        let committee: Committee = bcs::from_bytes(blob.bytes())?;
1374        Ok(Some(Arc::new(committee)))
1375    }
1376
1377    async fn contains_blob(&self, blob_id: BlobId) -> Result<bool, ViewError> {
1378        Ok(self.blobs.pin().contains_key(&blob_id))
1379    }
1380
1381    async fn contains_event(&self, event_id: EventId) -> Result<bool, ViewError> {
1382        Ok(self.events.pin().contains_key(&event_id))
1383    }
1384
1385    #[cfg(with_testing)]
1386    async fn add_blobs(
1387        &self,
1388        blobs: impl IntoIterator<Item = Blob> + Send,
1389    ) -> Result<(), ViewError> {
1390        let pinned = self.blobs.pin();
1391        for blob in blobs {
1392            pinned.insert(blob.id(), blob);
1393        }
1394
1395        Ok(())
1396    }
1397
1398    #[cfg(with_testing)]
1399    async fn add_events(
1400        &self,
1401        events: impl IntoIterator<Item = (EventId, Vec<u8>)> + Send,
1402    ) -> Result<(), ViewError> {
1403        let pinned = self.events.pin();
1404        for (event_id, bytes) in events {
1405            pinned.insert(event_id, bytes);
1406        }
1407
1408        Ok(())
1409    }
1410}
1411
1412impl From<SystemOperation> for Operation {
1413    fn from(operation: SystemOperation) -> Self {
1414        Operation::System(Box::new(operation))
1415    }
1416}
1417
1418impl Operation {
1419    pub fn system(operation: SystemOperation) -> Self {
1420        Operation::System(Box::new(operation))
1421    }
1422
1423    /// Creates a new user application operation following the `application_id`'s [`Abi`].
1424    #[cfg(with_testing)]
1425    pub fn user<A: Abi>(
1426        application_id: ApplicationId<A>,
1427        operation: &A::Operation,
1428    ) -> Result<Self, bcs::Error> {
1429        Self::user_without_abi(application_id.forget_abi(), operation)
1430    }
1431
1432    /// Creates a new user application operation assuming that the `operation` is valid for the
1433    /// `application_id`.
1434    #[cfg(with_testing)]
1435    pub fn user_without_abi(
1436        application_id: ApplicationId,
1437        operation: &impl Serialize,
1438    ) -> Result<Self, bcs::Error> {
1439        Ok(Operation::User {
1440            application_id,
1441            bytes: bcs::to_bytes(&operation)?,
1442        })
1443    }
1444
1445    /// Returns a reference to the [`SystemOperation`] in this [`Operation`], if this [`Operation`]
1446    /// is for the system application.
1447    pub fn as_system_operation(&self) -> Option<&SystemOperation> {
1448        match self {
1449            Operation::System(system_operation) => Some(system_operation),
1450            Operation::User { .. } => None,
1451        }
1452    }
1453
1454    pub fn application_id(&self) -> GenericApplicationId {
1455        match self {
1456            Self::System(_) => GenericApplicationId::System,
1457            Self::User { application_id, .. } => GenericApplicationId::User(*application_id),
1458        }
1459    }
1460
1461    /// Returns the IDs of all blobs published in this operation.
1462    pub fn published_blob_ids(&self) -> Vec<BlobId> {
1463        match self.as_system_operation() {
1464            Some(SystemOperation::PublishDataBlob { blob_hash }) => {
1465                vec![BlobId::new(*blob_hash, BlobType::Data)]
1466            }
1467            Some(SystemOperation::Admin(AdminOperation::PublishCommitteeBlob { blob_hash })) => {
1468                vec![BlobId::new(*blob_hash, BlobType::Committee)]
1469            }
1470            Some(SystemOperation::PublishModule { module_id }) => module_id.bytecode_blob_ids(),
1471            _ => vec![],
1472        }
1473    }
1474
1475    /// Returns whether this operation is allowed regardless of application permissions.
1476    pub fn is_exempt_from_permissions(&self) -> bool {
1477        let Operation::System(system_op) = self else {
1478            return false;
1479        };
1480        matches!(
1481            **system_op,
1482            SystemOperation::ProcessNewEpoch(_)
1483                | SystemOperation::ProcessRemovedEpoch(_)
1484                | SystemOperation::UpdateStreams(_)
1485        )
1486    }
1487}
1488
1489impl From<SystemMessage> for Message {
1490    fn from(message: SystemMessage) -> Self {
1491        Message::System(message)
1492    }
1493}
1494
1495impl Message {
1496    pub fn system(message: SystemMessage) -> Self {
1497        Message::System(message)
1498    }
1499
1500    /// Creates a new user application message assuming that the `message` is valid for the
1501    /// `application_id`.
1502    pub fn user<A, M: Serialize>(
1503        application_id: ApplicationId<A>,
1504        message: &M,
1505    ) -> Result<Self, bcs::Error> {
1506        let application_id = application_id.forget_abi();
1507        let bytes = bcs::to_bytes(&message)?;
1508        Ok(Message::User {
1509            application_id,
1510            bytes,
1511        })
1512    }
1513
1514    pub fn application_id(&self) -> GenericApplicationId {
1515        match self {
1516            Self::System(_) => GenericApplicationId::System,
1517            Self::User { application_id, .. } => GenericApplicationId::User(*application_id),
1518        }
1519    }
1520}
1521
1522impl From<SystemQuery> for Query {
1523    fn from(query: SystemQuery) -> Self {
1524        Query::System(query)
1525    }
1526}
1527
1528impl Query {
1529    pub fn system(query: SystemQuery) -> Self {
1530        Query::System(query)
1531    }
1532
1533    /// Creates a new user application query following the `application_id`'s [`Abi`].
1534    pub fn user<A: Abi>(
1535        application_id: ApplicationId<A>,
1536        query: &A::Query,
1537    ) -> Result<Self, serde_json::Error> {
1538        Self::user_without_abi(application_id.forget_abi(), query)
1539    }
1540
1541    /// Creates a new user application query assuming that the `query` is valid for the
1542    /// `application_id`.
1543    pub fn user_without_abi(
1544        application_id: ApplicationId,
1545        query: &impl Serialize,
1546    ) -> Result<Self, serde_json::Error> {
1547        Ok(Query::User {
1548            application_id,
1549            bytes: serde_json::to_vec(&query)?,
1550        })
1551    }
1552
1553    pub fn application_id(&self) -> GenericApplicationId {
1554        match self {
1555            Self::System(_) => GenericApplicationId::System,
1556            Self::User { application_id, .. } => GenericApplicationId::User(*application_id),
1557        }
1558    }
1559}
1560
1561impl From<SystemResponse> for QueryResponse {
1562    fn from(response: SystemResponse) -> Self {
1563        QueryResponse::System(response)
1564    }
1565}
1566
1567impl From<Vec<u8>> for QueryResponse {
1568    fn from(response: Vec<u8>) -> Self {
1569        QueryResponse::User(response)
1570    }
1571}
1572
1573/// The state of a blob of binary data.
1574#[derive(Eq, PartialEq, Debug, Hash, Clone, Serialize, Deserialize)]
1575pub struct BlobState {
1576    /// Hash of the last `Certificate` that published or used this blob. If empty, the
1577    /// blob is known to be published by a confirmed certificate but we may not have fully
1578    /// processed this certificate just yet.
1579    pub last_used_by: Option<CryptoHash>,
1580    /// The `ChainId` of the chain that published the change
1581    pub chain_id: ChainId,
1582    /// The `BlockHeight` of the chain that published the change
1583    pub block_height: BlockHeight,
1584    /// Epoch of the `last_used_by` certificate (if any).
1585    pub epoch: Option<Epoch>,
1586}
1587
1588/// The runtime to use for running the application.
1589#[derive(Clone, Copy, Display)]
1590#[cfg_attr(with_wasm_runtime, derive(Debug, Default))]
1591pub enum WasmRuntime {
1592    #[cfg(with_wasmer)]
1593    #[default]
1594    #[display("wasmer")]
1595    Wasmer,
1596    #[cfg(with_wasmtime)]
1597    #[cfg_attr(not(with_wasmer), default)]
1598    #[display("wasmtime")]
1599    Wasmtime,
1600}
1601
1602#[derive(Clone, Copy, Display)]
1603#[cfg_attr(with_revm, derive(Debug, Default))]
1604pub enum EvmRuntime {
1605    #[cfg(with_revm)]
1606    #[default]
1607    #[display("revm")]
1608    Revm,
1609}
1610
1611/// Trait used to select a default `WasmRuntime`, if one is available.
1612pub trait WithWasmDefault {
1613    fn with_wasm_default(self) -> Self;
1614}
1615
1616impl WithWasmDefault for Option<WasmRuntime> {
1617    fn with_wasm_default(self) -> Self {
1618        #[cfg(with_wasm_runtime)]
1619        {
1620            Some(self.unwrap_or_default())
1621        }
1622        #[cfg(not(with_wasm_runtime))]
1623        {
1624            None
1625        }
1626    }
1627}
1628
1629impl FromStr for WasmRuntime {
1630    type Err = InvalidWasmRuntime;
1631
1632    fn from_str(string: &str) -> Result<Self, Self::Err> {
1633        match string {
1634            #[cfg(with_wasmer)]
1635            "wasmer" => Ok(WasmRuntime::Wasmer),
1636            #[cfg(with_wasmtime)]
1637            "wasmtime" => Ok(WasmRuntime::Wasmtime),
1638            unknown => Err(InvalidWasmRuntime(unknown.to_owned())),
1639        }
1640    }
1641}
1642
1643/// Attempts to create an invalid [`WasmRuntime`] instance from a string.
1644#[derive(Clone, Debug, Error)]
1645#[error("{0:?} is not a valid WebAssembly runtime")]
1646pub struct InvalidWasmRuntime(String);
1647
1648doc_scalar!(Operation, "An operation to be executed in a block");
1649doc_scalar!(
1650    Message,
1651    "A message to be sent and possibly executed in the receiver's block."
1652);
1653doc_scalar!(MessageKind, "The kind of outgoing message being sent");