1#![deny(missing_docs)]
8
9pub mod committee;
11pub mod evm;
12mod execution;
13pub mod execution_state_actor;
14#[cfg(with_graphql)]
15mod graphql;
16mod policy;
17mod resources;
18mod runtime;
19pub mod system;
21#[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, ensure, 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::{EpochEventData, 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::{ProtocolFlag, ResourceControlPolicy},
77 resources::{BalanceHolder, ResourceController, ResourceTracker},
78 runtime::{
79 ContractSyncRuntimeHandle, ServiceRuntimeRequest, ServiceSyncRuntime,
80 ServiceSyncRuntimeHandle,
81 },
82 system::{
83 ChainProgress, SystemExecutionStateView, SystemMessage, SystemOperation, SystemQuery,
84 SystemResponse,
85 },
86 transaction_tracker::{PreparedCheckpoint, TransactionOutcome, TransactionTracker},
87};
88
89pub const LINERA_SOL: &str = include_str!("../solidity/Linera.sol");
92pub const LINERA_TYPES_SOL: &str = include_str!("../solidity/LineraTypes.sol");
95
96const MAX_STREAM_NAME_LEN: usize = 64;
98
99#[derive(Clone)]
101pub struct UserContractCode(Box<dyn UserContractModule>);
102
103#[derive(Clone)]
105pub struct UserServiceCode(Box<dyn UserServiceModule>);
106
107pub type UserContractInstance = Box<dyn UserContract>;
109
110pub type UserServiceInstance = Box<dyn UserService>;
112
113pub trait UserContractModule: dyn_clone::DynClone + Any + web_thread::Post + Send + Sync {
115 fn instantiate(
117 &self,
118 runtime: ContractSyncRuntimeHandle,
119 ) -> Result<UserContractInstance, ExecutionError>;
120}
121
122impl<T: UserContractModule + Send + Sync + 'static> From<T> for UserContractCode {
123 fn from(module: T) -> Self {
124 Self(Box::new(module))
125 }
126}
127
128dyn_clone::clone_trait_object!(UserContractModule);
129
130pub trait UserServiceModule: dyn_clone::DynClone + Any + web_thread::Post + Send + Sync {
132 fn instantiate(
134 &self,
135 runtime: ServiceSyncRuntimeHandle,
136 ) -> Result<UserServiceInstance, ExecutionError>;
137}
138
139impl<T: UserServiceModule + Send + Sync + 'static> From<T> for UserServiceCode {
140 fn from(module: T) -> Self {
141 Self(Box::new(module))
142 }
143}
144
145dyn_clone::clone_trait_object!(UserServiceModule);
146
147impl UserServiceCode {
148 fn instantiate(
149 &self,
150 runtime: ServiceSyncRuntimeHandle,
151 ) -> Result<UserServiceInstance, ExecutionError> {
152 self.0.instantiate(runtime)
153 }
154}
155
156impl UserContractCode {
157 fn instantiate(
158 &self,
159 runtime: ContractSyncRuntimeHandle,
160 ) -> Result<UserContractInstance, ExecutionError> {
161 self.0.instantiate(runtime)
162 }
163}
164
165pub struct JsVec<T>(pub Vec<T>);
167
168#[cfg(web)]
169const _: () = {
170 impl web_thread::AsJs for UserContractCode {
174 fn to_js(&self) -> Result<JsValue, JsValue> {
175 ((&*self.0) as &dyn Any)
176 .downcast_ref::<WasmContractModule>()
177 .expect("we only support Wasm modules on the Web for now")
178 .to_js()
179 }
180
181 fn from_js(value: JsValue) -> Result<Self, JsValue> {
182 WasmContractModule::from_js(value).map(Into::into)
183 }
184 }
185
186 impl web_thread::Post for UserContractCode {
187 fn transferables(&self) -> js_sys::Array {
188 self.0.transferables()
189 }
190 }
191
192 impl web_thread::AsJs for UserServiceCode {
193 fn to_js(&self) -> Result<JsValue, JsValue> {
194 ((&*self.0) as &dyn Any)
195 .downcast_ref::<WasmServiceModule>()
196 .expect("we only support Wasm modules on the Web for now")
197 .to_js()
198 }
199
200 fn from_js(value: JsValue) -> Result<Self, JsValue> {
201 WasmServiceModule::from_js(value).map(Into::into)
202 }
203 }
204
205 impl web_thread::Post for UserServiceCode {
206 fn transferables(&self) -> js_sys::Array {
207 self.0.transferables()
208 }
209 }
210
211 impl<T: web_thread::AsJs> web_thread::AsJs for JsVec<T> {
212 fn to_js(&self) -> Result<JsValue, JsValue> {
213 let array = self
214 .0
215 .iter()
216 .map(T::to_js)
217 .collect::<Result<js_sys::Array, _>>()?;
218 Ok(array.into())
219 }
220
221 fn from_js(value: JsValue) -> Result<Self, JsValue> {
222 let array = js_sys::Array::from(&value);
223 let v = array
224 .into_iter()
225 .map(T::from_js)
226 .collect::<Result<Vec<_>, _>>()?;
227 Ok(JsVec(v))
228 }
229 }
230
231 impl<T: web_thread::Post> web_thread::Post for JsVec<T> {
232 fn transferables(&self) -> js_sys::Array {
233 let mut array = js_sys::Array::new();
234 for x in &self.0 {
235 array = array.concat(&x.transferables());
236 }
237 array
238 }
239 }
240};
241
242#[derive(Error, Debug, strum::IntoStaticStr)]
244#[allow(missing_docs)]
245pub enum ExecutionError {
246 #[error(transparent)]
247 ViewError(#[from] ViewError),
248 #[error(transparent)]
249 ArithmeticError(#[from] ArithmeticError),
250 #[error("User application reported an error: {0}")]
251 UserError(String),
252 #[cfg(with_wasm_runtime)]
253 #[error(transparent)]
254 WasmError(#[from] WasmExecutionError),
255 #[cfg(with_revm)]
256 #[error(transparent)]
257 EvmError(#[from] EvmExecutionError),
258 #[error(transparent)]
259 DecompressionError(#[from] DecompressionError),
260 #[error("The given promise is invalid or was polled once already")]
261 InvalidPromise,
262
263 #[error("Attempted to perform a reentrant call to application {0}")]
264 ReentrantCall(ApplicationId),
265 #[error(
266 "Application {caller_id} attempted to perform a cross-application to {callee_id} call \
267 from `finalize`"
268 )]
269 CrossApplicationCallInFinalize {
270 caller_id: Box<ApplicationId>,
271 callee_id: Box<ApplicationId>,
272 },
273 #[error("Failed to load bytecode from storage {0:?}")]
274 ApplicationBytecodeNotFound(Box<ApplicationDescription>),
275 #[error("Unsupported dynamic application load: {0:?}")]
277 UnsupportedDynamicApplicationLoad(Box<ApplicationId>),
278
279 #[error("Excessive number of bytes read from storage")]
280 ExcessiveRead,
281 #[error("Excessive number of bytes written to storage")]
282 ExcessiveWrite,
283 #[error("Block execution required too much fuel for VM {0}")]
284 MaximumFuelExceeded(VmRuntime),
285 #[error("Services running as oracles in block took longer than allowed")]
286 MaximumServiceOracleExecutionTimeExceeded,
287 #[error("Service running as an oracle produced a response that's too large")]
288 ServiceOracleResponseTooLarge,
289 #[error("Serialized size of the block exceeds limit")]
290 BlockTooLarge,
291 #[error("HTTP response exceeds the size limit of {limit} bytes, having at least {size} bytes")]
292 HttpResponseSizeLimitExceeded { limit: u64, size: u64 },
293 #[error("Runtime failed to respond to application")]
294 MissingRuntimeResponse,
295 #[error("Application is not authorized to perform system operations on this chain: {0:}")]
296 UnauthorizedApplication(ApplicationId),
297 #[error("Failed to make network reqwest: {0}")]
298 ReqwestError(#[from] reqwest::Error),
299 #[error("Encountered I/O error: {0}")]
300 IoError(#[from] std::io::Error),
301 #[error("More recorded oracle responses than expected")]
302 UnexpectedOracleResponse,
303 #[error("Invalid JSON: {0}")]
304 JsonError(#[from] serde_json::Error),
305 #[error(transparent)]
306 BcsError(#[from] bcs::Error),
307 #[error("Recorded response for oracle query has the wrong type")]
308 OracleResponseMismatch,
309 #[error("Service oracle query tried to create operations: {0:?}")]
310 ServiceOracleQueryOperations(Vec<Operation>),
311 #[error("Assertion failed: local time {local_time} is not earlier than {timestamp}")]
312 AssertBefore {
313 timestamp: Timestamp,
314 local_time: Timestamp,
315 },
316
317 #[error("Stream names can be at most {MAX_STREAM_NAME_LEN} bytes.")]
318 StreamNameTooLong,
319 #[error("Blob exceeds size limit")]
320 BlobTooLarge,
321 #[error("Bytecode exceeds size limit")]
322 BytecodeTooLarge,
323 #[error("Attempt to perform an HTTP request to an unauthorized host: {0:?}")]
324 UnauthorizedHttpRequest(reqwest::Url),
325 #[error("Attempt to perform an HTTP request to an invalid URL")]
326 InvalidUrlForHttpRequest(#[from] url::ParseError),
327 #[error("Worker thread failure: {0:?}")]
328 Thread(#[from] web_thread::Error),
329 #[error("Blobs not found: {0:?}")]
330 BlobsNotFound(Vec<BlobId>),
331 #[error("Events not found: {0:?}")]
332 EventsNotFound(Vec<EventId>),
333
334 #[error("Invalid HTTP header name used for HTTP request")]
335 InvalidHeaderName(#[from] reqwest::header::InvalidHeaderName),
336 #[error("Invalid HTTP header value used for HTTP request")]
337 InvalidHeaderValue(#[from] reqwest::header::InvalidHeaderValue),
338
339 #[error("No NetworkDescription found in storage")]
340 NoNetworkDescriptionFound,
341 #[error("{epoch:?} is not recognized by chain {chain_id:}")]
342 InvalidEpoch { chain_id: ChainId, epoch: Epoch },
343 #[error("Transfer must have positive amount")]
344 IncorrectTransferAmount,
345 #[error("Transfer from owned account must be authenticated by the right owner")]
346 UnauthenticatedTransferOwner,
347 #[error("The transferred amount must not exceed the balance of the current account {account}: {balance}")]
348 InsufficientBalance {
349 balance: Amount,
350 account: AccountOwner,
351 },
352 #[error("Required execution fees exceeded the total funding available. Fees {fees}, available balance: {balance}")]
353 FeesExceedFunding { fees: Amount, balance: Amount },
354 #[error("Claim must have positive amount")]
355 IncorrectClaimAmount,
356 #[error("Claim must be authenticated by the right owner")]
357 UnauthenticatedClaimOwner,
358 #[error("The transferred amount must not exceed the allowance for spender {spender} from owner {owner}: {allowance}")]
359 InsufficientAllowance {
360 allowance: Amount,
361 owner: AccountOwner,
362 spender: AccountOwner,
363 },
364 #[error("Admin operations are only allowed on the admin chain.")]
365 AdminOperationOnNonAdminChain,
366 #[error("Failed to create new committee: expected {expected}, but got {provided}")]
367 InvalidCommitteeEpoch { expected: Epoch, provided: Epoch },
368 #[error("Failed to remove committee")]
369 InvalidCommitteeRemoval,
370 #[error("No recorded response for oracle query")]
371 MissingOracleResponse,
372 #[error("process_streams was not called for all stream updates")]
373 UnprocessedStreams,
374 #[error("Internal error: {0}")]
375 InternalError(&'static str),
376 #[error("UpdateStream is outdated")]
377 OutdatedUpdateStream,
378 #[error("UpdateStream references an application that is not subscribed")]
379 UnsubscribedUpdateStream,
380 #[error("Checkpoint precondition failed: {0}")]
381 CheckpointPreconditionFailed(&'static str),
382}
383
384impl ExecutionError {
385 pub fn is_local(&self) -> bool {
389 match self {
390 ExecutionError::ArithmeticError(_)
391 | ExecutionError::UserError(_)
392 | ExecutionError::DecompressionError(_)
393 | ExecutionError::InvalidPromise
394 | ExecutionError::CrossApplicationCallInFinalize { .. }
395 | ExecutionError::ReentrantCall(_)
396 | ExecutionError::ApplicationBytecodeNotFound(_)
397 | ExecutionError::UnsupportedDynamicApplicationLoad(_)
398 | ExecutionError::ExcessiveRead
399 | ExecutionError::ExcessiveWrite
400 | ExecutionError::MaximumFuelExceeded(_)
401 | ExecutionError::MaximumServiceOracleExecutionTimeExceeded
402 | ExecutionError::ServiceOracleResponseTooLarge
403 | ExecutionError::BlockTooLarge
404 | ExecutionError::HttpResponseSizeLimitExceeded { .. }
405 | ExecutionError::UnauthorizedApplication(_)
406 | ExecutionError::UnexpectedOracleResponse
407 | ExecutionError::JsonError(_)
408 | ExecutionError::BcsError(_)
409 | ExecutionError::OracleResponseMismatch
410 | ExecutionError::ServiceOracleQueryOperations(_)
411 | ExecutionError::AssertBefore { .. }
412 | ExecutionError::StreamNameTooLong
413 | ExecutionError::BlobTooLarge
414 | ExecutionError::BytecodeTooLarge
415 | ExecutionError::UnauthorizedHttpRequest(_)
416 | ExecutionError::InvalidUrlForHttpRequest(_)
417 | ExecutionError::BlobsNotFound(_)
418 | ExecutionError::EventsNotFound(_)
419 | ExecutionError::InvalidHeaderName(_)
420 | ExecutionError::InvalidHeaderValue(_)
421 | ExecutionError::InvalidEpoch { .. }
422 | ExecutionError::IncorrectTransferAmount
423 | ExecutionError::UnauthenticatedTransferOwner
424 | ExecutionError::InsufficientBalance { .. }
425 | ExecutionError::FeesExceedFunding { .. }
426 | ExecutionError::IncorrectClaimAmount
427 | ExecutionError::UnauthenticatedClaimOwner
428 | ExecutionError::InsufficientAllowance { .. }
429 | ExecutionError::AdminOperationOnNonAdminChain
430 | ExecutionError::InvalidCommitteeEpoch { .. }
431 | ExecutionError::InvalidCommitteeRemoval
432 | ExecutionError::MissingOracleResponse
433 | ExecutionError::UnprocessedStreams
434 | ExecutionError::OutdatedUpdateStream
435 | ExecutionError::UnsubscribedUpdateStream
436 | ExecutionError::CheckpointPreconditionFailed(_)
437 | ExecutionError::ViewError(ViewError::NotFound(_)) => false,
438 #[cfg(with_wasm_runtime)]
439 ExecutionError::WasmError(_) => false,
440 #[cfg(with_revm)]
441 ExecutionError::EvmError(..) => false,
442 ExecutionError::MissingRuntimeResponse
443 | ExecutionError::ViewError(_)
444 | ExecutionError::ReqwestError(_)
445 | ExecutionError::Thread(_)
446 | ExecutionError::NoNetworkDescriptionFound
447 | ExecutionError::InternalError(_)
448 | ExecutionError::IoError(_) => true,
449 }
450 }
451
452 pub fn error_type(&self) -> String {
455 let variant: &'static str = self.into();
456 format!("ExecutionError::{variant}")
457 }
458
459 pub fn is_limit_error(&self) -> bool {
464 matches!(
465 self,
466 ExecutionError::ExcessiveRead
467 | ExecutionError::ExcessiveWrite
468 | ExecutionError::MaximumFuelExceeded(_)
469 | ExecutionError::MaximumServiceOracleExecutionTimeExceeded
470 | ExecutionError::BlockTooLarge
471 )
472 }
473
474 pub fn is_transient_error(&self) -> bool {
480 matches!(
481 self,
482 ExecutionError::BlobsNotFound(_) | ExecutionError::EventsNotFound(_)
483 )
484 }
485}
486
487pub trait UserContract {
489 fn instantiate(&mut self, argument: Vec<u8>) -> Result<(), ExecutionError>;
491
492 fn execute_operation(&mut self, operation: Vec<u8>) -> Result<Vec<u8>, ExecutionError>;
494
495 fn execute_message(&mut self, message: Vec<u8>) -> Result<(), ExecutionError>;
497
498 fn process_streams(&mut self, updates: Vec<StreamUpdate>) -> Result<(), ExecutionError>;
500
501 fn summarize_events(&mut self, updates: Vec<StreamUpdate>) -> Result<(), ExecutionError>;
504
505 fn finalize(&mut self) -> Result<(), ExecutionError>;
507}
508
509pub trait UserService {
511 fn handle_query(&mut self, argument: Vec<u8>) -> Result<Vec<u8>, ExecutionError>;
513}
514
515#[derive(Clone, Copy)]
517pub struct ExecutionRuntimeConfig {
518 pub allow_application_logs: bool,
521}
522
523impl Default for ExecutionRuntimeConfig {
524 fn default() -> Self {
525 Self {
526 allow_application_logs: true,
527 }
528 }
529}
530
531#[cfg_attr(not(web), async_trait)]
534#[cfg_attr(web, async_trait(?Send))]
535pub trait ExecutionRuntimeContext {
536 fn chain_id(&self) -> ChainId;
538
539 fn thread_pool(&self) -> &Arc<ThreadPool>;
541
542 fn execution_runtime_config(&self) -> ExecutionRuntimeConfig;
544
545 fn user_contracts(&self) -> &Arc<papaya::HashMap<ApplicationId, UserContractCode>>;
547
548 fn user_services(&self) -> &Arc<papaya::HashMap<ApplicationId, UserServiceCode>>;
550
551 async fn get_user_contract(
553 &self,
554 description: &ApplicationDescription,
555 txn_tracker: &TransactionTracker,
556 ) -> Result<UserContractCode, ExecutionError>;
557
558 async fn get_user_service(
560 &self,
561 description: &ApplicationDescription,
562 txn_tracker: &TransactionTracker,
563 ) -> Result<UserServiceCode, ExecutionError>;
564
565 async fn get_blob(&self, blob_id: BlobId) -> Result<Option<Arc<Blob>>, ViewError>;
567
568 async fn get_event(&self, event_id: EventId) -> Result<Option<Arc<Vec<u8>>>, ViewError>;
570
571 async fn get_network_description(&self) -> Result<Option<NetworkDescription>, ViewError>;
573
574 async fn get_or_load_committee_by_hash(
582 &self,
583 hash: CryptoHash,
584 ) -> Result<Arc<Committee>, ExecutionError>;
585
586 async fn get_committee_hashes(
588 &self,
589 epoch_range: RangeInclusive<Epoch>,
590 ) -> Result<BTreeMap<Epoch, CryptoHash>, ExecutionError> {
591 let net_description = self
592 .get_network_description()
593 .await?
594 .ok_or(ExecutionError::NoNetworkDescriptionFound)?;
595 let committee_hashes = futures::future::join_all(
596 (epoch_range.start().0..=epoch_range.end().0).map(|epoch| async move {
597 if epoch == 0 {
598 Ok((Epoch(epoch), net_description.genesis_committee_blob_hash))
600 } else {
601 let event_id = EventId {
602 chain_id: net_description.admin_chain_id,
603 stream_id: StreamId::system(EPOCH_STREAM_NAME),
604 index: epoch,
605 };
606 let event = self
607 .get_event(event_id.clone())
608 .await?
609 .ok_or_else(|| ExecutionError::EventsNotFound(vec![event_id]))?;
610 let event_data: EpochEventData = bcs::from_bytes(&event)?;
611 Ok((Epoch(epoch), event_data.blob_hash))
612 }
613 }),
614 )
615 .await;
616 let missing_events = committee_hashes
617 .iter()
618 .filter_map(|result| {
619 if let Err(ExecutionError::EventsNotFound(event_ids)) = result {
620 return Some(event_ids);
621 }
622 None
623 })
624 .flatten()
625 .cloned()
626 .collect::<Vec<_>>();
627 ensure!(
628 missing_events.is_empty(),
629 ExecutionError::EventsNotFound(missing_events)
630 );
631 committee_hashes.into_iter().collect()
632 }
633
634 async fn contains_blob(&self, blob_id: BlobId) -> Result<bool, ViewError>;
636
637 async fn contains_event(&self, event_id: EventId) -> Result<bool, ViewError>;
639
640 #[cfg(with_testing)]
642 async fn add_blobs(
643 &self,
644 blobs: impl IntoIterator<Item = Blob> + Send,
645 ) -> Result<(), ViewError>;
646
647 #[cfg(with_testing)]
649 async fn add_events(
650 &self,
651 events: impl IntoIterator<Item = (EventId, Vec<u8>)> + Send,
652 ) -> Result<(), ViewError>;
653}
654
655#[derive(Clone, Copy, Debug)]
657pub struct OperationContext {
658 pub chain_id: ChainId,
660 #[debug(skip_if = Option::is_none)]
662 pub authenticated_owner: Option<AccountOwner>,
663 pub height: BlockHeight,
665 pub round: Option<u32>,
667 pub timestamp: Timestamp,
669}
670
671#[derive(Clone, Copy, Debug)]
673pub struct MessageContext {
674 pub chain_id: ChainId,
676 pub origin: ChainId,
678 pub origin_timestamp: Timestamp,
680 pub is_bouncing: bool,
682 #[debug(skip_if = Option::is_none)]
684 pub authenticated_owner: Option<AccountOwner>,
685 #[debug(skip_if = Option::is_none)]
687 pub refund_grant_to: Option<Account>,
688 pub height: BlockHeight,
690 pub round: Option<u32>,
692 pub timestamp: Timestamp,
694}
695
696#[derive(Clone, Copy, Debug)]
698pub struct ProcessStreamsContext {
699 pub chain_id: ChainId,
701 pub height: BlockHeight,
703 pub round: Option<u32>,
705 pub timestamp: Timestamp,
707}
708
709impl From<MessageContext> for ProcessStreamsContext {
710 fn from(context: MessageContext) -> Self {
711 Self {
712 chain_id: context.chain_id,
713 height: context.height,
714 round: context.round,
715 timestamp: context.timestamp,
716 }
717 }
718}
719
720impl From<OperationContext> for ProcessStreamsContext {
721 fn from(context: OperationContext) -> Self {
722 Self {
723 chain_id: context.chain_id,
724 height: context.height,
725 round: context.round,
726 timestamp: context.timestamp,
727 }
728 }
729}
730
731#[derive(Clone, Copy, Debug)]
733pub struct FinalizeContext {
734 pub chain_id: ChainId,
736 #[debug(skip_if = Option::is_none)]
738 pub authenticated_owner: Option<AccountOwner>,
739 pub height: BlockHeight,
741 pub round: Option<u32>,
743}
744
745#[derive(Clone, Copy, Debug, Eq, PartialEq)]
747pub struct QueryContext {
748 pub chain_id: ChainId,
750 pub next_block_height: BlockHeight,
752 pub local_time: Timestamp,
754}
755
756pub trait BaseRuntime {
758 type Read: fmt::Debug + Send + Sync;
760 type ContainsKey: fmt::Debug + Send + Sync;
762 type ContainsKeys: fmt::Debug + Send + Sync;
764 type ReadMultiValuesBytes: fmt::Debug + Send + Sync;
766 type ReadValueBytes: fmt::Debug + Send + Sync;
768 type FindKeysByPrefix: fmt::Debug + Send + Sync;
770 type FindKeyValuesByPrefix: fmt::Debug + Send + Sync;
772
773 fn chain_id(&mut self) -> Result<ChainId, ExecutionError>;
775
776 fn block_height(&mut self) -> Result<BlockHeight, ExecutionError>;
778
779 fn application_id(&mut self) -> Result<ApplicationId, ExecutionError>;
781
782 fn application_creator_chain_id(&mut self) -> Result<ChainId, ExecutionError>;
784
785 fn read_application_description(
787 &mut self,
788 application_id: ApplicationId,
789 ) -> Result<ApplicationDescription, ExecutionError>;
790
791 fn application_parameters(&mut self) -> Result<Vec<u8>, ExecutionError>;
793
794 fn read_system_timestamp(&mut self) -> Result<Timestamp, ExecutionError>;
796
797 fn read_chain_balance(&mut self) -> Result<Amount, ExecutionError>;
799
800 fn read_owner_balance(&mut self, owner: AccountOwner) -> Result<Amount, ExecutionError>;
802
803 fn read_owner_balances(&mut self) -> Result<Vec<(AccountOwner, Amount)>, ExecutionError>;
805
806 fn read_balance_owners(&mut self) -> Result<Vec<AccountOwner>, ExecutionError>;
808
809 fn read_allowance(
811 &mut self,
812 owner: AccountOwner,
813 spender: AccountOwner,
814 ) -> Result<Amount, ExecutionError>;
815
816 fn read_allowances(
818 &mut self,
819 ) -> Result<Vec<(AccountOwner, AccountOwner, Amount)>, ExecutionError>;
820
821 fn chain_ownership(&mut self) -> Result<ChainOwnership, ExecutionError>;
823
824 fn application_permissions(&mut self) -> Result<ApplicationPermissions, ExecutionError>;
826
827 #[cfg(feature = "test")]
829 fn contains_key(&mut self, key: Vec<u8>) -> Result<bool, ExecutionError> {
830 let promise = self.contains_key_new(key)?;
831 self.contains_key_wait(&promise)
832 }
833
834 fn contains_key_new(&mut self, key: Vec<u8>) -> Result<Self::ContainsKey, ExecutionError>;
836
837 fn contains_key_wait(&mut self, promise: &Self::ContainsKey) -> Result<bool, ExecutionError>;
839
840 #[cfg(feature = "test")]
842 fn contains_keys(&mut self, keys: Vec<Vec<u8>>) -> Result<Vec<bool>, ExecutionError> {
843 let promise = self.contains_keys_new(keys)?;
844 self.contains_keys_wait(&promise)
845 }
846
847 fn contains_keys_new(
849 &mut self,
850 keys: Vec<Vec<u8>>,
851 ) -> Result<Self::ContainsKeys, ExecutionError>;
852
853 fn contains_keys_wait(
855 &mut self,
856 promise: &Self::ContainsKeys,
857 ) -> Result<Vec<bool>, ExecutionError>;
858
859 #[cfg(feature = "test")]
861 fn read_multi_values_bytes(
862 &mut self,
863 keys: Vec<Vec<u8>>,
864 ) -> Result<Vec<Option<Vec<u8>>>, ExecutionError> {
865 let promise = self.read_multi_values_bytes_new(keys)?;
866 self.read_multi_values_bytes_wait(&promise)
867 }
868
869 fn read_multi_values_bytes_new(
871 &mut self,
872 keys: Vec<Vec<u8>>,
873 ) -> Result<Self::ReadMultiValuesBytes, ExecutionError>;
874
875 fn read_multi_values_bytes_wait(
877 &mut self,
878 promise: &Self::ReadMultiValuesBytes,
879 ) -> Result<Vec<Option<Vec<u8>>>, ExecutionError>;
880
881 #[cfg(feature = "test")]
883 fn read_value_bytes(&mut self, key: Vec<u8>) -> Result<Option<Vec<u8>>, ExecutionError> {
884 let promise = self.read_value_bytes_new(key)?;
885 self.read_value_bytes_wait(&promise)
886 }
887
888 fn read_value_bytes_new(
890 &mut self,
891 key: Vec<u8>,
892 ) -> Result<Self::ReadValueBytes, ExecutionError>;
893
894 fn read_value_bytes_wait(
896 &mut self,
897 promise: &Self::ReadValueBytes,
898 ) -> Result<Option<Vec<u8>>, ExecutionError>;
899
900 fn find_keys_by_prefix_new(
902 &mut self,
903 key_prefix: Vec<u8>,
904 ) -> Result<Self::FindKeysByPrefix, ExecutionError>;
905
906 fn find_keys_by_prefix_wait(
908 &mut self,
909 promise: &Self::FindKeysByPrefix,
910 ) -> Result<Vec<Vec<u8>>, ExecutionError>;
911
912 #[cfg(feature = "test")]
914 #[expect(clippy::type_complexity)]
915 fn find_key_values_by_prefix(
916 &mut self,
917 key_prefix: Vec<u8>,
918 ) -> Result<Vec<(Vec<u8>, Vec<u8>)>, ExecutionError> {
919 let promise = self.find_key_values_by_prefix_new(key_prefix)?;
920 self.find_key_values_by_prefix_wait(&promise)
921 }
922
923 fn find_key_values_by_prefix_new(
925 &mut self,
926 key_prefix: Vec<u8>,
927 ) -> Result<Self::FindKeyValuesByPrefix, ExecutionError>;
928
929 #[expect(clippy::type_complexity)]
931 fn find_key_values_by_prefix_wait(
932 &mut self,
933 promise: &Self::FindKeyValuesByPrefix,
934 ) -> Result<Vec<(Vec<u8>, Vec<u8>)>, ExecutionError>;
935
936 fn perform_http_request(
938 &mut self,
939 request: http::Request,
940 ) -> Result<http::Response, ExecutionError>;
941
942 fn assert_before(&mut self, timestamp: Timestamp) -> Result<(), ExecutionError>;
948
949 fn read_data_blob(&mut self, hash: DataBlobHash) -> Result<Vec<u8>, ExecutionError>;
951
952 fn assert_data_blob_exists(&mut self, hash: DataBlobHash) -> Result<(), ExecutionError>;
954
955 fn has_empty_storage(&mut self, application: ApplicationId) -> Result<bool, ExecutionError>;
957
958 fn maximum_blob_size(&mut self) -> Result<u64, ExecutionError>;
960
961 fn allow_application_logs(&mut self) -> Result<bool, ExecutionError>;
964
965 #[cfg(web)]
968 fn send_log(&mut self, message: String, level: tracing::log::Level);
969}
970
971pub trait ServiceRuntime: BaseRuntime {
973 fn try_query_application(
975 &mut self,
976 queried_id: ApplicationId,
977 argument: Vec<u8>,
978 ) -> Result<Vec<u8>, ExecutionError>;
979
980 fn schedule_operation(&mut self, operation: Vec<u8>) -> Result<(), ExecutionError>;
982
983 fn check_execution_time(&mut self) -> Result<(), ExecutionError>;
985}
986
987pub trait ContractRuntime: BaseRuntime {
989 fn authenticated_owner(&mut self) -> Result<Option<AccountOwner>, ExecutionError>;
991
992 fn message_is_bouncing(&mut self) -> Result<Option<bool>, ExecutionError>;
995
996 fn message_origin_chain_id(&mut self) -> Result<Option<ChainId>, ExecutionError>;
998
999 fn message_origin_timestamp(&mut self) -> Result<Option<Timestamp>, ExecutionError>;
1002
1003 fn authenticated_caller_id(&mut self) -> Result<Option<ApplicationId>, ExecutionError>;
1006
1007 fn maximum_fuel_per_block(&mut self, vm_runtime: VmRuntime) -> Result<u64, ExecutionError>;
1009
1010 fn remaining_fuel(&mut self, vm_runtime: VmRuntime) -> Result<u64, ExecutionError>;
1012
1013 fn consume_fuel(&mut self, fuel: u64, vm_runtime: VmRuntime) -> Result<(), ExecutionError>;
1015
1016 fn send_message(&mut self, message: SendMessageRequest<Vec<u8>>) -> Result<(), ExecutionError>;
1018
1019 fn transfer(
1021 &mut self,
1022 source: AccountOwner,
1023 destination: Account,
1024 amount: Amount,
1025 ) -> Result<(), ExecutionError>;
1026
1027 fn claim(
1029 &mut self,
1030 source: Account,
1031 destination: Account,
1032 amount: Amount,
1033 ) -> Result<(), ExecutionError>;
1034
1035 fn approve(
1037 &mut self,
1038 owner: AccountOwner,
1039 spender: AccountOwner,
1040 amount: Amount,
1041 ) -> Result<(), ExecutionError>;
1042
1043 fn transfer_from(
1045 &mut self,
1046 owner: AccountOwner,
1047 spender: AccountOwner,
1048 destination: Account,
1049 amount: Amount,
1050 ) -> Result<(), ExecutionError>;
1051
1052 fn try_call_application(
1055 &mut self,
1056 authenticated: bool,
1057 callee_id: ApplicationId,
1058 argument: Vec<u8>,
1059 ) -> Result<Vec<u8>, ExecutionError>;
1060
1061 fn emit(&mut self, name: StreamName, value: Vec<u8>) -> Result<u32, ExecutionError>;
1063
1064 fn read_event(
1068 &mut self,
1069 chain_id: ChainId,
1070 stream_name: StreamName,
1071 index: u32,
1072 ) -> Result<Vec<u8>, ExecutionError>;
1073
1074 fn subscribe_to_events(
1076 &mut self,
1077 chain_id: ChainId,
1078 application_id: ApplicationId,
1079 stream_name: StreamName,
1080 ) -> Result<(), ExecutionError>;
1081
1082 fn unsubscribe_from_events(
1084 &mut self,
1085 chain_id: ChainId,
1086 application_id: ApplicationId,
1087 stream_name: StreamName,
1088 ) -> Result<(), ExecutionError>;
1089
1090 fn query_service(
1092 &mut self,
1093 application_id: ApplicationId,
1094 query: Vec<u8>,
1095 ) -> Result<Vec<u8>, ExecutionError>;
1096
1097 fn open_chain(
1099 &mut self,
1100 ownership: ChainOwnership,
1101 application_permissions: ApplicationPermissions,
1102 balance: Amount,
1103 ) -> Result<ChainId, ExecutionError>;
1104
1105 fn close_chain(&mut self) -> Result<(), ExecutionError>;
1107
1108 fn change_ownership(&mut self, ownership: ChainOwnership) -> Result<(), ExecutionError>;
1110
1111 fn change_application_permissions(
1113 &mut self,
1114 application_permissions: ApplicationPermissions,
1115 ) -> Result<(), ExecutionError>;
1116
1117 fn create_application(
1119 &mut self,
1120 module_id: ModuleId,
1121 parameters: Vec<u8>,
1122 argument: Vec<u8>,
1123 required_application_ids: Vec<ApplicationId>,
1124 ) -> Result<ApplicationId, ExecutionError>;
1125
1126 fn peek_application_index(&mut self) -> Result<u32, ExecutionError>;
1129
1130 fn create_data_blob(&mut self, bytes: Vec<u8>) -> Result<DataBlobHash, ExecutionError>;
1132
1133 fn publish_module(
1136 &mut self,
1137 contract: Bytecode,
1138 service: Bytecode,
1139 vm_runtime: VmRuntime,
1140 formats: Option<Vec<u8>>,
1141 ) -> Result<ModuleId, ExecutionError>;
1142
1143 fn validation_round(&mut self) -> Result<Option<u32>, ExecutionError>;
1145
1146 fn write_batch(&mut self, batch: Batch) -> Result<(), ExecutionError>;
1148}
1149
1150#[derive(
1152 Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, Allocative, strum::AsRefStr,
1153)]
1154pub enum Operation {
1155 System(Box<SystemOperation>),
1157 User {
1159 application_id: ApplicationId,
1161 #[serde(with = "serde_bytes")]
1163 #[debug(with = "hex_debug")]
1164 bytes: Vec<u8>,
1165 },
1166}
1167
1168impl BcsHashable<'_> for Operation {}
1169
1170#[derive(
1172 Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, Allocative, strum::AsRefStr,
1173)]
1174pub enum Message {
1175 System(SystemMessage),
1177 User {
1179 application_id: ApplicationId,
1181 #[serde(with = "serde_bytes")]
1183 #[debug(with = "hex_debug")]
1184 bytes: Vec<u8>,
1185 },
1186}
1187
1188#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
1190pub enum Query {
1191 System(SystemQuery),
1193 User {
1195 application_id: ApplicationId,
1197 #[serde(with = "serde_bytes")]
1199 #[debug(with = "hex_debug")]
1200 bytes: Vec<u8>,
1201 },
1202}
1203
1204#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
1206pub struct QueryOutcome<Response = QueryResponse> {
1207 pub response: Response,
1209 pub operations: Vec<Operation>,
1211}
1212
1213impl From<QueryOutcome<SystemResponse>> for QueryOutcome {
1214 fn from(system_outcome: QueryOutcome<SystemResponse>) -> Self {
1215 let QueryOutcome {
1216 response,
1217 operations,
1218 } = system_outcome;
1219
1220 QueryOutcome {
1221 response: QueryResponse::System(response),
1222 operations,
1223 }
1224 }
1225}
1226
1227impl From<QueryOutcome<Vec<u8>>> for QueryOutcome {
1228 fn from(user_service_outcome: QueryOutcome<Vec<u8>>) -> Self {
1229 let QueryOutcome {
1230 response,
1231 operations,
1232 } = user_service_outcome;
1233
1234 QueryOutcome {
1235 response: QueryResponse::User(response),
1236 operations,
1237 }
1238 }
1239}
1240
1241#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
1243pub enum QueryResponse {
1244 System(SystemResponse),
1246 User(
1248 #[serde(with = "serde_bytes")]
1249 #[debug(with = "hex_debug")]
1250 Vec<u8>,
1251 ),
1252}
1253
1254#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, Copy, Allocative)]
1256pub enum MessageKind {
1257 Simple,
1259 Protected,
1262 Tracked,
1265 Bouncing,
1267}
1268
1269impl Display for MessageKind {
1270 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1271 match self {
1272 MessageKind::Simple => write!(f, "Simple"),
1273 MessageKind::Protected => write!(f, "Protected"),
1274 MessageKind::Tracked => write!(f, "Tracked"),
1275 MessageKind::Bouncing => write!(f, "Bouncing"),
1276 }
1277 }
1278}
1279
1280#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, SimpleObject, Allocative)]
1282pub struct OutgoingMessage {
1283 pub destination: ChainId,
1285 #[debug(skip_if = Option::is_none)]
1287 pub authenticated_owner: Option<AccountOwner>,
1288 #[debug(skip_if = Amount::is_zero)]
1290 pub grant: Amount,
1291 #[debug(skip_if = Option::is_none)]
1293 pub refund_grant_to: Option<Account>,
1294 pub kind: MessageKind,
1296 pub message: Message,
1298}
1299
1300impl BcsHashable<'_> for OutgoingMessage {}
1301
1302impl OutgoingMessage {
1303 pub fn new(recipient: ChainId, message: impl Into<Message>) -> Self {
1305 OutgoingMessage {
1306 destination: recipient,
1307 authenticated_owner: None,
1308 grant: Amount::ZERO,
1309 refund_grant_to: None,
1310 kind: MessageKind::Simple,
1311 message: message.into(),
1312 }
1313 }
1314
1315 pub fn with_kind(mut self, kind: MessageKind) -> Self {
1317 self.kind = kind;
1318 self
1319 }
1320
1321 pub fn with_authenticated_owner(mut self, authenticated_owner: Option<AccountOwner>) -> Self {
1323 self.authenticated_owner = authenticated_owner;
1324 self
1325 }
1326}
1327
1328impl OperationContext {
1329 fn refund_grant_to(&self) -> Option<Account> {
1332 self.authenticated_owner.map(|owner| Account {
1333 chain_id: self.chain_id,
1334 owner,
1335 })
1336 }
1337}
1338
1339#[cfg(with_testing)]
1341#[derive(Clone)]
1342pub struct TestExecutionRuntimeContext {
1343 chain_id: ChainId,
1344 thread_pool: Arc<ThreadPool>,
1345 execution_runtime_config: ExecutionRuntimeConfig,
1346 user_contracts: Arc<papaya::HashMap<ApplicationId, UserContractCode>>,
1347 user_services: Arc<papaya::HashMap<ApplicationId, UserServiceCode>>,
1348 blobs: Arc<papaya::HashMap<BlobId, Blob>>,
1349 events: Arc<papaya::HashMap<EventId, Vec<u8>>>,
1350}
1351
1352#[cfg(with_testing)]
1353impl TestExecutionRuntimeContext {
1354 pub fn new(chain_id: ChainId, execution_runtime_config: ExecutionRuntimeConfig) -> Self {
1356 Self {
1357 chain_id,
1358 thread_pool: Arc::new(ThreadPool::new(20)),
1359 execution_runtime_config,
1360 user_contracts: Arc::default(),
1361 user_services: Arc::default(),
1362 blobs: Arc::default(),
1363 events: Arc::default(),
1364 }
1365 }
1366}
1367
1368#[cfg(with_testing)]
1369#[cfg_attr(not(web), async_trait)]
1370#[cfg_attr(web, async_trait(?Send))]
1371impl ExecutionRuntimeContext for TestExecutionRuntimeContext {
1372 fn chain_id(&self) -> ChainId {
1373 self.chain_id
1374 }
1375
1376 fn thread_pool(&self) -> &Arc<ThreadPool> {
1377 &self.thread_pool
1378 }
1379
1380 fn execution_runtime_config(&self) -> ExecutionRuntimeConfig {
1381 self.execution_runtime_config
1382 }
1383
1384 fn user_contracts(&self) -> &Arc<papaya::HashMap<ApplicationId, UserContractCode>> {
1385 &self.user_contracts
1386 }
1387
1388 fn user_services(&self) -> &Arc<papaya::HashMap<ApplicationId, UserServiceCode>> {
1389 &self.user_services
1390 }
1391
1392 async fn get_user_contract(
1393 &self,
1394 description: &ApplicationDescription,
1395 _txn_tracker: &TransactionTracker,
1396 ) -> Result<UserContractCode, ExecutionError> {
1397 let application_id: ApplicationId = description.into();
1398 let pinned = self.user_contracts().pin();
1399 Ok(pinned
1400 .get(&application_id)
1401 .ok_or_else(|| {
1402 ExecutionError::ApplicationBytecodeNotFound(Box::new(description.clone()))
1403 })?
1404 .clone())
1405 }
1406
1407 async fn get_user_service(
1408 &self,
1409 description: &ApplicationDescription,
1410 _txn_tracker: &TransactionTracker,
1411 ) -> Result<UserServiceCode, ExecutionError> {
1412 let application_id: ApplicationId = description.into();
1413 let pinned = self.user_services().pin();
1414 Ok(pinned
1415 .get(&application_id)
1416 .ok_or_else(|| {
1417 ExecutionError::ApplicationBytecodeNotFound(Box::new(description.clone()))
1418 })?
1419 .clone())
1420 }
1421
1422 async fn get_blob(&self, blob_id: BlobId) -> Result<Option<Arc<Blob>>, ViewError> {
1423 Ok(self.blobs.pin().get(&blob_id).cloned().map(Arc::new))
1424 }
1425
1426 async fn get_event(&self, event_id: EventId) -> Result<Option<Arc<Vec<u8>>>, ViewError> {
1427 Ok(self.events.pin().get(&event_id).cloned().map(Arc::new))
1428 }
1429
1430 async fn get_network_description(&self) -> Result<Option<NetworkDescription>, ViewError> {
1431 let pinned = self.blobs.pin();
1432 let genesis_committee_blob_hash = pinned
1433 .iter()
1434 .find(|(_, blob)| blob.content().blob_type() == BlobType::Committee)
1435 .map_or_else(
1436 || CryptoHash::test_hash("genesis committee"),
1437 |(_, blob)| blob.id().hash,
1438 );
1439 Ok(Some(NetworkDescription {
1440 admin_chain_id: dummy_chain_description(0).id(),
1441 genesis_config_hash: CryptoHash::test_hash("genesis config"),
1442 genesis_timestamp: Timestamp::from(0),
1443 genesis_committee_blob_hash,
1444 name: "dummy network description".to_string(),
1445 }))
1446 }
1447
1448 async fn get_or_load_committee_by_hash(
1449 &self,
1450 hash: CryptoHash,
1451 ) -> Result<Arc<Committee>, ExecutionError> {
1452 let blob_id = BlobId::new(hash, BlobType::Committee);
1453 let blob = self
1454 .blobs
1455 .pin()
1456 .get(&blob_id)
1457 .cloned()
1458 .ok_or(ExecutionError::BlobsNotFound(vec![blob_id]))?;
1459 let committee: Committee = bcs::from_bytes(blob.bytes())?;
1460 Ok(Arc::new(committee))
1461 }
1462
1463 async fn contains_blob(&self, blob_id: BlobId) -> Result<bool, ViewError> {
1464 Ok(self.blobs.pin().contains_key(&blob_id))
1465 }
1466
1467 async fn contains_event(&self, event_id: EventId) -> Result<bool, ViewError> {
1468 Ok(self.events.pin().contains_key(&event_id))
1469 }
1470
1471 #[cfg(with_testing)]
1472 async fn add_blobs(
1473 &self,
1474 blobs: impl IntoIterator<Item = Blob> + Send,
1475 ) -> Result<(), ViewError> {
1476 let pinned = self.blobs.pin();
1477 for blob in blobs {
1478 pinned.insert(blob.id(), blob);
1479 }
1480
1481 Ok(())
1482 }
1483
1484 #[cfg(with_testing)]
1485 async fn add_events(
1486 &self,
1487 events: impl IntoIterator<Item = (EventId, Vec<u8>)> + Send,
1488 ) -> Result<(), ViewError> {
1489 let pinned = self.events.pin();
1490 for (event_id, bytes) in events {
1491 pinned.insert(event_id, bytes);
1492 }
1493
1494 Ok(())
1495 }
1496}
1497
1498impl From<SystemOperation> for Operation {
1499 fn from(operation: SystemOperation) -> Self {
1500 Operation::System(Box::new(operation))
1501 }
1502}
1503
1504impl Operation {
1505 pub fn system(operation: SystemOperation) -> Self {
1507 Operation::System(Box::new(operation))
1508 }
1509
1510 #[cfg(with_testing)]
1512 pub fn user<A: Abi>(
1513 application_id: ApplicationId<A>,
1514 operation: &A::Operation,
1515 ) -> Result<Self, bcs::Error> {
1516 Self::user_without_abi(application_id.forget_abi(), operation)
1517 }
1518
1519 #[cfg(with_testing)]
1522 pub fn user_without_abi(
1523 application_id: ApplicationId,
1524 operation: &impl Serialize,
1525 ) -> Result<Self, bcs::Error> {
1526 Ok(Operation::User {
1527 application_id,
1528 bytes: bcs::to_bytes(&operation)?,
1529 })
1530 }
1531
1532 pub fn as_system_operation(&self) -> Option<&SystemOperation> {
1535 match self {
1536 Operation::System(system_operation) => Some(system_operation),
1537 Operation::User { .. } => None,
1538 }
1539 }
1540
1541 pub fn application_id(&self) -> GenericApplicationId {
1543 match self {
1544 Self::System(_) => GenericApplicationId::System,
1545 Self::User { application_id, .. } => GenericApplicationId::User(*application_id),
1546 }
1547 }
1548
1549 pub fn published_blob_ids(&self) -> Vec<BlobId> {
1551 match self.as_system_operation() {
1552 Some(SystemOperation::PublishDataBlob { blob_hash }) => {
1553 vec![BlobId::new(*blob_hash, BlobType::Data)]
1554 }
1555 Some(SystemOperation::Admin(AdminOperation::PublishCommitteeBlob { blob_hash })) => {
1556 vec![BlobId::new(*blob_hash, BlobType::Committee)]
1557 }
1558 Some(SystemOperation::PublishModule { module_id }) => module_id.bytecode_blob_ids(),
1559 _ => vec![],
1560 }
1561 }
1562
1563 pub fn is_exempt_from_permissions(&self) -> bool {
1565 let Operation::System(system_op) = self else {
1566 return false;
1567 };
1568 matches!(
1569 **system_op,
1570 SystemOperation::ProcessNewEpoch(_) | SystemOperation::UpdateStream { .. }
1571 )
1572 }
1573
1574 pub fn is_update_stream(&self) -> bool {
1576 let Operation::System(system_op) = self else {
1577 return false;
1578 };
1579 matches!(**system_op, SystemOperation::UpdateStream { .. })
1580 }
1581
1582 pub fn is_checkpoint(&self) -> bool {
1584 let Operation::System(system_op) = self else {
1585 return false;
1586 };
1587 matches!(**system_op, SystemOperation::Checkpoint)
1588 }
1589}
1590
1591impl From<SystemMessage> for Message {
1592 fn from(message: SystemMessage) -> Self {
1593 Message::System(message)
1594 }
1595}
1596
1597impl Message {
1598 pub fn system(message: SystemMessage) -> Self {
1600 Message::System(message)
1601 }
1602
1603 pub fn is_checkpoint_ack(&self) -> bool {
1605 matches!(self, Message::System(SystemMessage::CheckpointAck { .. }))
1606 }
1607
1608 pub fn user<A, M: Serialize>(
1611 application_id: ApplicationId<A>,
1612 message: &M,
1613 ) -> Result<Self, bcs::Error> {
1614 let application_id = application_id.forget_abi();
1615 let bytes = bcs::to_bytes(&message)?;
1616 Ok(Message::User {
1617 application_id,
1618 bytes,
1619 })
1620 }
1621
1622 pub fn application_id(&self) -> GenericApplicationId {
1624 match self {
1625 Self::System(_) => GenericApplicationId::System,
1626 Self::User { application_id, .. } => GenericApplicationId::User(*application_id),
1627 }
1628 }
1629}
1630
1631impl From<SystemQuery> for Query {
1632 fn from(query: SystemQuery) -> Self {
1633 Query::System(query)
1634 }
1635}
1636
1637impl Query {
1638 pub fn system(query: SystemQuery) -> Self {
1640 Query::System(query)
1641 }
1642
1643 pub fn user<A: Abi>(
1645 application_id: ApplicationId<A>,
1646 query: &A::Query,
1647 ) -> Result<Self, serde_json::Error> {
1648 Self::user_without_abi(application_id.forget_abi(), query)
1649 }
1650
1651 pub fn user_without_abi(
1654 application_id: ApplicationId,
1655 query: &impl Serialize,
1656 ) -> Result<Self, serde_json::Error> {
1657 Ok(Query::User {
1658 application_id,
1659 bytes: serde_json::to_vec(&query)?,
1660 })
1661 }
1662
1663 pub fn application_id(&self) -> GenericApplicationId {
1665 match self {
1666 Self::System(_) => GenericApplicationId::System,
1667 Self::User { application_id, .. } => GenericApplicationId::User(*application_id),
1668 }
1669 }
1670}
1671
1672impl From<SystemResponse> for QueryResponse {
1673 fn from(response: SystemResponse) -> Self {
1674 QueryResponse::System(response)
1675 }
1676}
1677
1678impl From<Vec<u8>> for QueryResponse {
1679 fn from(response: Vec<u8>) -> Self {
1680 QueryResponse::User(response)
1681 }
1682}
1683
1684#[derive(Eq, PartialEq, Debug, Hash, Clone, Serialize, Deserialize)]
1688pub enum BlobOrigin {
1689 Genesis,
1694 Published {
1697 chain_id: ChainId,
1699 block_height: BlockHeight,
1701 },
1702}
1703
1704#[derive(Eq, PartialEq, Debug, Hash, Clone, Serialize, Deserialize)]
1706pub struct BlobState {
1707 pub origin: BlobOrigin,
1709 pub last_used_by: Option<CryptoHash>,
1713 pub epoch: Option<Epoch>,
1715}
1716
1717impl BlobState {
1718 pub const GENESIS: BlobState = BlobState {
1721 origin: BlobOrigin::Genesis,
1722 last_used_by: None,
1723 epoch: None,
1724 };
1725}
1726
1727#[derive(Clone, Copy, Display)]
1729#[cfg_attr(with_wasm_runtime, derive(Debug, Default))]
1730#[allow(missing_docs)]
1731pub enum WasmRuntime {
1732 #[cfg(with_wasmer)]
1733 #[default]
1734 #[display("wasmer")]
1735 Wasmer,
1736 #[cfg(with_wasmtime)]
1737 #[cfg_attr(not(with_wasmer), default)]
1738 #[display("wasmtime")]
1739 Wasmtime,
1740}
1741
1742#[derive(Clone, Copy, Display)]
1744#[cfg_attr(with_revm, derive(Debug, Default))]
1745#[allow(missing_docs)]
1746pub enum EvmRuntime {
1747 #[cfg(with_revm)]
1748 #[default]
1749 #[display("revm")]
1750 Revm,
1751}
1752
1753pub trait WithWasmDefault {
1755 fn with_wasm_default(self) -> Self;
1757}
1758
1759impl WithWasmDefault for Option<WasmRuntime> {
1760 fn with_wasm_default(self) -> Self {
1761 #[cfg(with_wasm_runtime)]
1762 {
1763 Some(self.unwrap_or_default())
1764 }
1765 #[cfg(not(with_wasm_runtime))]
1766 {
1767 None
1768 }
1769 }
1770}
1771
1772impl FromStr for WasmRuntime {
1773 type Err = InvalidWasmRuntime;
1774
1775 fn from_str(string: &str) -> Result<Self, Self::Err> {
1776 match string {
1777 #[cfg(with_wasmer)]
1778 "wasmer" => Ok(WasmRuntime::Wasmer),
1779 #[cfg(with_wasmtime)]
1780 "wasmtime" => Ok(WasmRuntime::Wasmtime),
1781 unknown => Err(InvalidWasmRuntime(unknown.to_owned())),
1782 }
1783 }
1784}
1785
1786#[derive(Clone, Debug, Error)]
1788#[error("{0:?} is not a valid WebAssembly runtime")]
1789pub struct InvalidWasmRuntime(String);
1790
1791doc_scalar!(Operation, "An operation to be executed in a block");
1792doc_scalar!(
1793 Message,
1794 "A message to be sent and possibly executed in the receiver's block."
1795);
1796doc_scalar!(MessageKind, "The kind of outgoing message being sent");