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