1pub mod committee;
8pub mod evm;
9mod execution;
10pub mod execution_state_actor;
11#[cfg(with_graphql)]
12mod graphql;
13mod policy;
14mod resources;
15mod runtime;
16pub mod system;
17#[cfg(with_testing)]
18pub mod test_utils;
19mod transaction_tracker;
20mod util;
21mod wasm;
22
23use std::{any::Any, collections::BTreeMap, fmt, ops::RangeInclusive, str::FromStr, sync::Arc};
24
25use allocative::Allocative;
26use async_graphql::SimpleObject;
27use async_trait::async_trait;
28use custom_debug_derive::Debug;
29use derive_more::Display;
30#[cfg(web)]
31use js_sys::wasm_bindgen::JsValue;
32use linera_base::{
33 abi::Abi,
34 crypto::{BcsHashable, CryptoHash},
35 data_types::{
36 Amount, ApplicationDescription, ApplicationPermissions, ArithmeticError, Blob, BlockHeight,
37 Bytecode, DecompressionError, Epoch, NetworkDescription, SendMessageRequest, StreamUpdate,
38 Timestamp,
39 },
40 doc_scalar, hex_debug, http,
41 identifiers::{
42 Account, AccountOwner, ApplicationId, BlobId, BlobType, ChainId, DataBlobHash, EventId,
43 GenericApplicationId, ModuleId, StreamName,
44 },
45 ownership::ChainOwnership,
46 task,
47 vm::VmRuntime,
48};
49use linera_views::{batch::Batch, ViewError};
50use serde::{Deserialize, Serialize};
51use system::AdminOperation;
52use thiserror::Error;
53
54#[cfg(with_revm)]
55use crate::evm::EvmExecutionError;
56#[cfg(with_testing)]
57use crate::test_utils::dummy_chain_description;
58#[cfg(all(with_testing, with_wasm_runtime))]
59pub use crate::wasm::test as wasm_test;
60#[cfg(with_wasm_runtime)]
61pub use crate::wasm::{
62 BaseRuntimeApi, ContractEntrypoints, ContractRuntimeApi, RuntimeApiData, ServiceEntrypoints,
63 ServiceRuntimeApi, WasmContractModule, WasmExecutionError, WasmServiceModule,
64};
65pub use crate::{
66 committee::Committee,
67 execution::{ExecutionStateView, ServiceRuntimeEndpoint},
68 execution_state_actor::{ExecutionRequest, ExecutionStateActor},
69 policy::ResourceControlPolicy,
70 resources::{BalanceHolder, ResourceController, ResourceTracker},
71 runtime::{
72 ContractSyncRuntimeHandle, ServiceRuntimeRequest, ServiceSyncRuntime,
73 ServiceSyncRuntimeHandle,
74 },
75 system::{
76 SystemExecutionStateView, SystemMessage, SystemOperation, SystemQuery, SystemResponse,
77 },
78 transaction_tracker::{TransactionOutcome, TransactionTracker},
79};
80
81pub const LINERA_SOL: &str = include_str!("../solidity/Linera.sol");
84pub const LINERA_TYPES_SOL: &str = include_str!("../solidity/LineraTypes.sol");
85
86const MAX_STREAM_NAME_LEN: usize = 64;
88
89pub const FLAG_ZERO_HASH: &str = "FLAG_ZERO_HASH.linera.network";
94pub const FLAG_FREE_REJECT: &str = "FLAG_FREE_REJECT.linera.network";
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 + task::Post + Send + Sync {
115 fn instantiate(
116 &self,
117 runtime: ContractSyncRuntimeHandle,
118 ) -> Result<UserContractInstance, ExecutionError>;
119}
120
121impl<T: UserContractModule + Send + Sync + 'static> From<T> for UserContractCode {
122 fn from(module: T) -> Self {
123 Self(Box::new(module))
124 }
125}
126
127dyn_clone::clone_trait_object!(UserContractModule);
128
129pub trait UserServiceModule: dyn_clone::DynClone + Any + task::Post + Send + Sync {
131 fn instantiate(
132 &self,
133 runtime: ServiceSyncRuntimeHandle,
134 ) -> Result<UserServiceInstance, ExecutionError>;
135}
136
137impl<T: UserServiceModule + Send + Sync + 'static> From<T> for UserServiceCode {
138 fn from(module: T) -> Self {
139 Self(Box::new(module))
140 }
141}
142
143dyn_clone::clone_trait_object!(UserServiceModule);
144
145impl UserServiceCode {
146 fn instantiate(
147 &self,
148 runtime: ServiceSyncRuntimeHandle,
149 ) -> Result<UserServiceInstance, ExecutionError> {
150 self.0.instantiate(runtime)
151 }
152}
153
154impl UserContractCode {
155 fn instantiate(
156 &self,
157 runtime: ContractSyncRuntimeHandle,
158 ) -> Result<UserContractInstance, ExecutionError> {
159 self.0.instantiate(runtime)
160 }
161}
162
163#[cfg(web)]
164const _: () = {
165 impl From<UserContractCode> for JsValue {
169 fn from(code: UserContractCode) -> JsValue {
170 let module: WasmContractModule = *(code.0 as Box<dyn Any>)
171 .downcast()
172 .expect("we only support Wasm modules on the Web for now");
173 module.into()
174 }
175 }
176
177 impl From<UserServiceCode> for JsValue {
178 fn from(code: UserServiceCode) -> JsValue {
179 let module: WasmServiceModule = *(code.0 as Box<dyn Any>)
180 .downcast()
181 .expect("we only support Wasm modules on the Web for now");
182 module.into()
183 }
184 }
185
186 impl TryFrom<JsValue> for UserContractCode {
187 type Error = JsValue;
188 fn try_from(value: JsValue) -> Result<Self, JsValue> {
189 WasmContractModule::try_from(value).map(Into::into)
190 }
191 }
192
193 impl TryFrom<JsValue> for UserServiceCode {
194 type Error = JsValue;
195 fn try_from(value: JsValue) -> Result<Self, JsValue> {
196 WasmServiceModule::try_from(value).map(Into::into)
197 }
198 }
199};
200
201#[derive(Error, Debug)]
203pub enum ExecutionError {
204 #[error(transparent)]
205 ViewError(#[from] ViewError),
206 #[error(transparent)]
207 ArithmeticError(#[from] ArithmeticError),
208 #[error("User application reported an error: {0}")]
209 UserError(String),
210 #[cfg(with_wasm_runtime)]
211 #[error(transparent)]
212 WasmError(#[from] WasmExecutionError),
213 #[cfg(with_revm)]
214 #[error(transparent)]
215 EvmError(#[from] EvmExecutionError),
216 #[error(transparent)]
217 DecompressionError(#[from] DecompressionError),
218 #[error("The given promise is invalid or was polled once already")]
219 InvalidPromise,
220
221 #[error("Attempted to perform a reentrant call to application {0}")]
222 ReentrantCall(ApplicationId),
223 #[error(
224 "Application {caller_id} attempted to perform a cross-application to {callee_id} call \
225 from `finalize`"
226 )]
227 CrossApplicationCallInFinalize {
228 caller_id: Box<ApplicationId>,
229 callee_id: Box<ApplicationId>,
230 },
231 #[error("Failed to load bytecode from storage {0:?}")]
232 ApplicationBytecodeNotFound(Box<ApplicationDescription>),
233 #[error("Unsupported dynamic application load: {0:?}")]
235 UnsupportedDynamicApplicationLoad(Box<ApplicationId>),
236
237 #[error("Excessive number of bytes read from storage")]
238 ExcessiveRead,
239 #[error("Excessive number of bytes written to storage")]
240 ExcessiveWrite,
241 #[error("Block execution required too much fuel for VM {0}")]
242 MaximumFuelExceeded(VmRuntime),
243 #[error("Services running as oracles in block took longer than allowed")]
244 MaximumServiceOracleExecutionTimeExceeded,
245 #[error("Service running as an oracle produced a response that's too large")]
246 ServiceOracleResponseTooLarge,
247 #[error("Serialized size of the block exceeds limit")]
248 BlockTooLarge,
249 #[error("HTTP response exceeds the size limit of {limit} bytes, having at least {size} bytes")]
250 HttpResponseSizeLimitExceeded { limit: u64, size: u64 },
251 #[error("Runtime failed to respond to application")]
252 MissingRuntimeResponse,
253 #[error("Application is not authorized to perform system operations on this chain: {0:}")]
254 UnauthorizedApplication(ApplicationId),
255 #[error("Failed to make network reqwest: {0}")]
256 ReqwestError(#[from] reqwest::Error),
257 #[error("Encountered I/O error: {0}")]
258 IoError(#[from] std::io::Error),
259 #[error("More recorded oracle responses than expected")]
260 UnexpectedOracleResponse,
261 #[error("Invalid JSON: {0}")]
262 JsonError(#[from] serde_json::Error),
263 #[error(transparent)]
264 BcsError(#[from] bcs::Error),
265 #[error("Recorded response for oracle query has the wrong type")]
266 OracleResponseMismatch,
267 #[error("Service oracle query tried to create operations: {0:?}")]
268 ServiceOracleQueryOperations(Vec<Operation>),
269 #[error("Assertion failed: local time {local_time} is not earlier than {timestamp}")]
270 AssertBefore {
271 timestamp: Timestamp,
272 local_time: Timestamp,
273 },
274
275 #[error("Stream names can be at most {MAX_STREAM_NAME_LEN} bytes.")]
276 StreamNameTooLong,
277 #[error("Blob exceeds size limit")]
278 BlobTooLarge,
279 #[error("Bytecode exceeds size limit")]
280 BytecodeTooLarge,
281 #[error("Attempt to perform an HTTP request to an unauthorized host: {0:?}")]
282 UnauthorizedHttpRequest(reqwest::Url),
283 #[error("Attempt to perform an HTTP request to an invalid URL")]
284 InvalidUrlForHttpRequest(#[from] url::ParseError),
285 #[error("Failed to send contract code to worker thread: {0:?}")]
286 ContractModuleSend(#[from] linera_base::task::SendError<UserContractCode>),
287 #[error("Failed to send service code to worker thread: {0:?}")]
288 ServiceModuleSend(#[from] linera_base::task::SendError<UserServiceCode>),
289 #[error("The chain being queried is not active {0}")]
290 InactiveChain(ChainId),
291 #[error("Blobs not found: {0:?}")]
292 BlobsNotFound(Vec<BlobId>),
293 #[error("Events not found: {0:?}")]
294 EventsNotFound(Vec<EventId>),
295
296 #[error("Invalid HTTP header name used for HTTP request")]
297 InvalidHeaderName(#[from] reqwest::header::InvalidHeaderName),
298 #[error("Invalid HTTP header value used for HTTP request")]
299 InvalidHeaderValue(#[from] reqwest::header::InvalidHeaderValue),
300
301 #[error("No NetworkDescription found in storage")]
302 NoNetworkDescriptionFound,
303 #[error("{epoch:?} is not recognized by chain {chain_id:}")]
304 InvalidEpoch { chain_id: ChainId, epoch: Epoch },
305 #[error("Transfer must have positive amount")]
306 IncorrectTransferAmount,
307 #[error("Transfer from owned account must be authenticated by the right signer")]
308 UnauthenticatedTransferOwner,
309 #[error("The transferred amount must not exceed the balance of the current account {account}: {balance}")]
310 InsufficientBalance {
311 balance: Amount,
312 account: AccountOwner,
313 },
314 #[error("Required execution fees exceeded the total funding available. Fees {fees}, available balance: {balance}")]
315 FeesExceedFunding { fees: Amount, balance: Amount },
316 #[error("Claim must have positive amount")]
317 IncorrectClaimAmount,
318 #[error("Claim must be authenticated by the right signer")]
319 UnauthenticatedClaimOwner,
320 #[error("Admin operations are only allowed on the admin chain.")]
321 AdminOperationOnNonAdminChain,
322 #[error("Failed to create new committee: expected {expected}, but got {provided}")]
323 InvalidCommitteeEpoch { expected: Epoch, provided: Epoch },
324 #[error("Failed to remove committee")]
325 InvalidCommitteeRemoval,
326 #[error("No recorded response for oracle query")]
327 MissingOracleResponse,
328 #[error("process_streams was not called for all stream updates")]
329 UnprocessedStreams,
330 #[error("Internal error: {0}")]
331 InternalError(&'static str),
332 #[error("UpdateStreams is outdated")]
333 OutdatedUpdateStreams,
334}
335
336impl ExecutionError {
337 pub fn is_local(&self) -> bool {
341 match self {
342 ExecutionError::ArithmeticError(_)
343 | ExecutionError::UserError(_)
344 | ExecutionError::DecompressionError(_)
345 | ExecutionError::InvalidPromise
346 | ExecutionError::CrossApplicationCallInFinalize { .. }
347 | ExecutionError::ReentrantCall(_)
348 | ExecutionError::ApplicationBytecodeNotFound(_)
349 | ExecutionError::UnsupportedDynamicApplicationLoad(_)
350 | ExecutionError::ExcessiveRead
351 | ExecutionError::ExcessiveWrite
352 | ExecutionError::MaximumFuelExceeded(_)
353 | ExecutionError::MaximumServiceOracleExecutionTimeExceeded
354 | ExecutionError::ServiceOracleResponseTooLarge
355 | ExecutionError::BlockTooLarge
356 | ExecutionError::HttpResponseSizeLimitExceeded { .. }
357 | ExecutionError::UnauthorizedApplication(_)
358 | ExecutionError::UnexpectedOracleResponse
359 | ExecutionError::JsonError(_)
360 | ExecutionError::BcsError(_)
361 | ExecutionError::OracleResponseMismatch
362 | ExecutionError::ServiceOracleQueryOperations(_)
363 | ExecutionError::AssertBefore { .. }
364 | ExecutionError::StreamNameTooLong
365 | ExecutionError::BlobTooLarge
366 | ExecutionError::BytecodeTooLarge
367 | ExecutionError::UnauthorizedHttpRequest(_)
368 | ExecutionError::InvalidUrlForHttpRequest(_)
369 | ExecutionError::InactiveChain(_)
370 | ExecutionError::BlobsNotFound(_)
371 | ExecutionError::EventsNotFound(_)
372 | ExecutionError::InvalidHeaderName(_)
373 | ExecutionError::InvalidHeaderValue(_)
374 | ExecutionError::InvalidEpoch { .. }
375 | ExecutionError::IncorrectTransferAmount
376 | ExecutionError::UnauthenticatedTransferOwner
377 | ExecutionError::InsufficientBalance { .. }
378 | ExecutionError::FeesExceedFunding { .. }
379 | ExecutionError::IncorrectClaimAmount
380 | ExecutionError::UnauthenticatedClaimOwner
381 | ExecutionError::AdminOperationOnNonAdminChain
382 | ExecutionError::InvalidCommitteeEpoch { .. }
383 | ExecutionError::InvalidCommitteeRemoval
384 | ExecutionError::MissingOracleResponse
385 | ExecutionError::UnprocessedStreams
386 | ExecutionError::OutdatedUpdateStreams
387 | ExecutionError::ViewError(ViewError::NotFound(_)) => false,
388 #[cfg(with_wasm_runtime)]
389 ExecutionError::WasmError(_) => false,
390 #[cfg(with_revm)]
391 ExecutionError::EvmError(..) => false,
392 ExecutionError::MissingRuntimeResponse
393 | ExecutionError::ViewError(_)
394 | ExecutionError::ReqwestError(_)
395 | ExecutionError::ContractModuleSend(_)
396 | ExecutionError::ServiceModuleSend(_)
397 | ExecutionError::NoNetworkDescriptionFound
398 | ExecutionError::InternalError(_)
399 | ExecutionError::IoError(_) => true,
400 }
401 }
402}
403
404pub trait UserContract {
406 fn instantiate(&mut self, argument: Vec<u8>) -> Result<(), ExecutionError>;
408
409 fn execute_operation(&mut self, operation: Vec<u8>) -> Result<Vec<u8>, ExecutionError>;
411
412 fn execute_message(&mut self, message: Vec<u8>) -> Result<(), ExecutionError>;
414
415 fn process_streams(&mut self, updates: Vec<StreamUpdate>) -> Result<(), ExecutionError>;
417
418 fn finalize(&mut self) -> Result<(), ExecutionError>;
420}
421
422pub trait UserService {
424 fn handle_query(&mut self, argument: Vec<u8>) -> Result<Vec<u8>, ExecutionError>;
426}
427
428#[derive(Clone, Copy, Default)]
430pub struct ExecutionRuntimeConfig {}
431
432#[cfg_attr(not(web), async_trait)]
435#[cfg_attr(web, async_trait(?Send))]
436pub trait ExecutionRuntimeContext {
437 fn chain_id(&self) -> ChainId;
438
439 fn execution_runtime_config(&self) -> ExecutionRuntimeConfig;
440
441 fn user_contracts(&self) -> &Arc<papaya::HashMap<ApplicationId, UserContractCode>>;
442
443 fn user_services(&self) -> &Arc<papaya::HashMap<ApplicationId, UserServiceCode>>;
444
445 async fn get_user_contract(
446 &self,
447 description: &ApplicationDescription,
448 txn_tracker: &TransactionTracker,
449 ) -> Result<UserContractCode, ExecutionError>;
450
451 async fn get_user_service(
452 &self,
453 description: &ApplicationDescription,
454 txn_tracker: &TransactionTracker,
455 ) -> Result<UserServiceCode, ExecutionError>;
456
457 async fn get_blob(&self, blob_id: BlobId) -> Result<Option<Blob>, ViewError>;
458
459 async fn get_event(&self, event_id: EventId) -> Result<Option<Vec<u8>>, ViewError>;
460
461 async fn get_network_description(&self) -> Result<Option<NetworkDescription>, ViewError>;
462
463 async fn committees_for(
464 &self,
465 epoch_range: RangeInclusive<Epoch>,
466 ) -> Result<BTreeMap<Epoch, Committee>, ViewError>;
467
468 async fn contains_blob(&self, blob_id: BlobId) -> Result<bool, ViewError>;
469
470 async fn contains_event(&self, event_id: EventId) -> Result<bool, ViewError>;
471
472 #[cfg(with_testing)]
473 async fn add_blobs(
474 &self,
475 blobs: impl IntoIterator<Item = Blob> + Send,
476 ) -> Result<(), ViewError>;
477
478 #[cfg(with_testing)]
479 async fn add_events(
480 &self,
481 events: impl IntoIterator<Item = (EventId, Vec<u8>)> + Send,
482 ) -> Result<(), ViewError>;
483}
484
485#[derive(Clone, Copy, Debug)]
486pub struct OperationContext {
487 pub chain_id: ChainId,
489 #[debug(skip_if = Option::is_none)]
491 pub authenticated_signer: Option<AccountOwner>,
492 pub height: BlockHeight,
494 pub round: Option<u32>,
496 pub timestamp: Timestamp,
498}
499
500#[derive(Clone, Copy, Debug)]
501pub struct MessageContext {
502 pub chain_id: ChainId,
504 pub origin: ChainId,
506 pub is_bouncing: bool,
508 #[debug(skip_if = Option::is_none)]
510 pub authenticated_signer: Option<AccountOwner>,
511 #[debug(skip_if = Option::is_none)]
513 pub refund_grant_to: Option<Account>,
514 pub height: BlockHeight,
516 pub round: Option<u32>,
518 pub timestamp: Timestamp,
520}
521
522#[derive(Clone, Copy, Debug)]
523pub struct ProcessStreamsContext {
524 pub chain_id: ChainId,
526 pub height: BlockHeight,
528 pub round: Option<u32>,
530 pub timestamp: Timestamp,
532}
533
534impl From<MessageContext> for ProcessStreamsContext {
535 fn from(context: MessageContext) -> Self {
536 Self {
537 chain_id: context.chain_id,
538 height: context.height,
539 round: context.round,
540 timestamp: context.timestamp,
541 }
542 }
543}
544
545impl From<OperationContext> for ProcessStreamsContext {
546 fn from(context: OperationContext) -> Self {
547 Self {
548 chain_id: context.chain_id,
549 height: context.height,
550 round: context.round,
551 timestamp: context.timestamp,
552 }
553 }
554}
555
556#[derive(Clone, Copy, Debug)]
557pub struct FinalizeContext {
558 pub chain_id: ChainId,
560 #[debug(skip_if = Option::is_none)]
562 pub authenticated_signer: Option<AccountOwner>,
563 pub height: BlockHeight,
565 pub round: Option<u32>,
567}
568
569#[derive(Clone, Copy, Debug, Eq, PartialEq)]
570pub struct QueryContext {
571 pub chain_id: ChainId,
573 pub next_block_height: BlockHeight,
575 pub local_time: Timestamp,
577}
578
579pub trait BaseRuntime {
580 type Read: fmt::Debug + Send + Sync;
581 type ContainsKey: fmt::Debug + Send + Sync;
582 type ContainsKeys: fmt::Debug + Send + Sync;
583 type ReadMultiValuesBytes: fmt::Debug + Send + Sync;
584 type ReadValueBytes: fmt::Debug + Send + Sync;
585 type FindKeysByPrefix: fmt::Debug + Send + Sync;
586 type FindKeyValuesByPrefix: fmt::Debug + Send + Sync;
587
588 fn chain_id(&mut self) -> Result<ChainId, ExecutionError>;
590
591 fn block_height(&mut self) -> Result<BlockHeight, ExecutionError>;
593
594 fn application_id(&mut self) -> Result<ApplicationId, ExecutionError>;
596
597 fn application_creator_chain_id(&mut self) -> Result<ChainId, ExecutionError>;
599
600 fn application_parameters(&mut self) -> Result<Vec<u8>, ExecutionError>;
602
603 fn read_system_timestamp(&mut self) -> Result<Timestamp, ExecutionError>;
605
606 fn read_chain_balance(&mut self) -> Result<Amount, ExecutionError>;
608
609 fn read_owner_balance(&mut self, owner: AccountOwner) -> Result<Amount, ExecutionError>;
611
612 fn read_owner_balances(&mut self) -> Result<Vec<(AccountOwner, Amount)>, ExecutionError>;
614
615 fn read_balance_owners(&mut self) -> Result<Vec<AccountOwner>, ExecutionError>;
617
618 fn chain_ownership(&mut self) -> Result<ChainOwnership, ExecutionError>;
620
621 #[cfg(feature = "test")]
623 fn contains_key(&mut self, key: Vec<u8>) -> Result<bool, ExecutionError> {
624 let promise = self.contains_key_new(key)?;
625 self.contains_key_wait(&promise)
626 }
627
628 fn contains_key_new(&mut self, key: Vec<u8>) -> Result<Self::ContainsKey, ExecutionError>;
630
631 fn contains_key_wait(&mut self, promise: &Self::ContainsKey) -> Result<bool, ExecutionError>;
633
634 #[cfg(feature = "test")]
636 fn contains_keys(&mut self, keys: Vec<Vec<u8>>) -> Result<Vec<bool>, ExecutionError> {
637 let promise = self.contains_keys_new(keys)?;
638 self.contains_keys_wait(&promise)
639 }
640
641 fn contains_keys_new(
643 &mut self,
644 keys: Vec<Vec<u8>>,
645 ) -> Result<Self::ContainsKeys, ExecutionError>;
646
647 fn contains_keys_wait(
649 &mut self,
650 promise: &Self::ContainsKeys,
651 ) -> Result<Vec<bool>, ExecutionError>;
652
653 #[cfg(feature = "test")]
655 fn read_multi_values_bytes(
656 &mut self,
657 keys: Vec<Vec<u8>>,
658 ) -> Result<Vec<Option<Vec<u8>>>, ExecutionError> {
659 let promise = self.read_multi_values_bytes_new(keys)?;
660 self.read_multi_values_bytes_wait(&promise)
661 }
662
663 fn read_multi_values_bytes_new(
665 &mut self,
666 keys: Vec<Vec<u8>>,
667 ) -> Result<Self::ReadMultiValuesBytes, ExecutionError>;
668
669 fn read_multi_values_bytes_wait(
671 &mut self,
672 promise: &Self::ReadMultiValuesBytes,
673 ) -> Result<Vec<Option<Vec<u8>>>, ExecutionError>;
674
675 #[cfg(feature = "test")]
677 fn read_value_bytes(&mut self, key: Vec<u8>) -> Result<Option<Vec<u8>>, ExecutionError> {
678 let promise = self.read_value_bytes_new(key)?;
679 self.read_value_bytes_wait(&promise)
680 }
681
682 fn read_value_bytes_new(
684 &mut self,
685 key: Vec<u8>,
686 ) -> Result<Self::ReadValueBytes, ExecutionError>;
687
688 fn read_value_bytes_wait(
690 &mut self,
691 promise: &Self::ReadValueBytes,
692 ) -> Result<Option<Vec<u8>>, ExecutionError>;
693
694 fn find_keys_by_prefix_new(
696 &mut self,
697 key_prefix: Vec<u8>,
698 ) -> Result<Self::FindKeysByPrefix, ExecutionError>;
699
700 fn find_keys_by_prefix_wait(
702 &mut self,
703 promise: &Self::FindKeysByPrefix,
704 ) -> Result<Vec<Vec<u8>>, ExecutionError>;
705
706 #[cfg(feature = "test")]
708 #[expect(clippy::type_complexity)]
709 fn find_key_values_by_prefix(
710 &mut self,
711 key_prefix: Vec<u8>,
712 ) -> Result<Vec<(Vec<u8>, Vec<u8>)>, ExecutionError> {
713 let promise = self.find_key_values_by_prefix_new(key_prefix)?;
714 self.find_key_values_by_prefix_wait(&promise)
715 }
716
717 fn find_key_values_by_prefix_new(
719 &mut self,
720 key_prefix: Vec<u8>,
721 ) -> Result<Self::FindKeyValuesByPrefix, ExecutionError>;
722
723 #[expect(clippy::type_complexity)]
725 fn find_key_values_by_prefix_wait(
726 &mut self,
727 promise: &Self::FindKeyValuesByPrefix,
728 ) -> Result<Vec<(Vec<u8>, Vec<u8>)>, ExecutionError>;
729
730 fn perform_http_request(
732 &mut self,
733 request: http::Request,
734 ) -> Result<http::Response, ExecutionError>;
735
736 fn assert_before(&mut self, timestamp: Timestamp) -> Result<(), ExecutionError>;
742
743 fn read_data_blob(&mut self, hash: DataBlobHash) -> Result<Vec<u8>, ExecutionError>;
745
746 fn assert_data_blob_exists(&mut self, hash: DataBlobHash) -> Result<(), ExecutionError>;
748}
749
750pub trait ServiceRuntime: BaseRuntime {
751 fn try_query_application(
753 &mut self,
754 queried_id: ApplicationId,
755 argument: Vec<u8>,
756 ) -> Result<Vec<u8>, ExecutionError>;
757
758 fn schedule_operation(&mut self, operation: Vec<u8>) -> Result<(), ExecutionError>;
760
761 fn check_execution_time(&mut self) -> Result<(), ExecutionError>;
763}
764
765pub trait ContractRuntime: BaseRuntime {
766 fn authenticated_signer(&mut self) -> Result<Option<AccountOwner>, ExecutionError>;
768
769 fn message_is_bouncing(&mut self) -> Result<Option<bool>, ExecutionError>;
772
773 fn message_origin_chain_id(&mut self) -> Result<Option<ChainId>, ExecutionError>;
775
776 fn authenticated_caller_id(&mut self) -> Result<Option<ApplicationId>, ExecutionError>;
779
780 fn maximum_fuel_per_block(&mut self, vm_runtime: VmRuntime) -> Result<u64, ExecutionError>;
782
783 fn remaining_fuel(&mut self, vm_runtime: VmRuntime) -> Result<u64, ExecutionError>;
785
786 fn consume_fuel(&mut self, fuel: u64, vm_runtime: VmRuntime) -> Result<(), ExecutionError>;
788
789 fn send_message(&mut self, message: SendMessageRequest<Vec<u8>>) -> Result<(), ExecutionError>;
791
792 fn transfer(
794 &mut self,
795 source: AccountOwner,
796 destination: Account,
797 amount: Amount,
798 ) -> Result<(), ExecutionError>;
799
800 fn claim(
802 &mut self,
803 source: Account,
804 destination: Account,
805 amount: Amount,
806 ) -> Result<(), ExecutionError>;
807
808 fn try_call_application(
811 &mut self,
812 authenticated: bool,
813 callee_id: ApplicationId,
814 argument: Vec<u8>,
815 ) -> Result<Vec<u8>, ExecutionError>;
816
817 fn emit(&mut self, name: StreamName, value: Vec<u8>) -> Result<u32, ExecutionError>;
819
820 fn read_event(
824 &mut self,
825 chain_id: ChainId,
826 stream_name: StreamName,
827 index: u32,
828 ) -> Result<Vec<u8>, ExecutionError>;
829
830 fn subscribe_to_events(
832 &mut self,
833 chain_id: ChainId,
834 application_id: ApplicationId,
835 stream_name: StreamName,
836 ) -> Result<(), ExecutionError>;
837
838 fn unsubscribe_from_events(
840 &mut self,
841 chain_id: ChainId,
842 application_id: ApplicationId,
843 stream_name: StreamName,
844 ) -> Result<(), ExecutionError>;
845
846 fn query_service(
848 &mut self,
849 application_id: ApplicationId,
850 query: Vec<u8>,
851 ) -> Result<Vec<u8>, ExecutionError>;
852
853 fn open_chain(
855 &mut self,
856 ownership: ChainOwnership,
857 application_permissions: ApplicationPermissions,
858 balance: Amount,
859 ) -> Result<ChainId, ExecutionError>;
860
861 fn close_chain(&mut self) -> Result<(), ExecutionError>;
863
864 fn change_application_permissions(
866 &mut self,
867 application_permissions: ApplicationPermissions,
868 ) -> Result<(), ExecutionError>;
869
870 fn create_application(
872 &mut self,
873 module_id: ModuleId,
874 parameters: Vec<u8>,
875 argument: Vec<u8>,
876 required_application_ids: Vec<ApplicationId>,
877 ) -> Result<ApplicationId, ExecutionError>;
878
879 fn create_data_blob(&mut self, bytes: Vec<u8>) -> Result<DataBlobHash, ExecutionError>;
881
882 fn publish_module(
884 &mut self,
885 contract: Bytecode,
886 service: Bytecode,
887 vm_runtime: VmRuntime,
888 ) -> Result<ModuleId, ExecutionError>;
889
890 fn validation_round(&mut self) -> Result<Option<u32>, ExecutionError>;
892
893 fn write_batch(&mut self, batch: Batch) -> Result<(), ExecutionError>;
895}
896
897#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, Allocative)]
899pub enum Operation {
900 System(Box<SystemOperation>),
902 User {
904 application_id: ApplicationId,
905 #[serde(with = "serde_bytes")]
906 #[debug(with = "hex_debug")]
907 bytes: Vec<u8>,
908 },
909}
910
911impl BcsHashable<'_> for Operation {}
912
913#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, Allocative)]
915pub enum Message {
916 System(SystemMessage),
918 User {
920 application_id: ApplicationId,
921 #[serde(with = "serde_bytes")]
922 #[debug(with = "hex_debug")]
923 bytes: Vec<u8>,
924 },
925}
926
927#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
929pub enum Query {
930 System(SystemQuery),
932 User {
934 application_id: ApplicationId,
935 #[serde(with = "serde_bytes")]
936 #[debug(with = "hex_debug")]
937 bytes: Vec<u8>,
938 },
939}
940
941#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
943pub struct QueryOutcome<Response = QueryResponse> {
944 pub response: Response,
945 pub operations: Vec<Operation>,
946}
947
948impl From<QueryOutcome<SystemResponse>> for QueryOutcome {
949 fn from(system_outcome: QueryOutcome<SystemResponse>) -> Self {
950 let QueryOutcome {
951 response,
952 operations,
953 } = system_outcome;
954
955 QueryOutcome {
956 response: QueryResponse::System(response),
957 operations,
958 }
959 }
960}
961
962impl From<QueryOutcome<Vec<u8>>> for QueryOutcome {
963 fn from(user_service_outcome: QueryOutcome<Vec<u8>>) -> Self {
964 let QueryOutcome {
965 response,
966 operations,
967 } = user_service_outcome;
968
969 QueryOutcome {
970 response: QueryResponse::User(response),
971 operations,
972 }
973 }
974}
975
976#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
978pub enum QueryResponse {
979 System(SystemResponse),
981 User(
983 #[serde(with = "serde_bytes")]
984 #[debug(with = "hex_debug")]
985 Vec<u8>,
986 ),
987}
988
989#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, Copy, Allocative)]
991pub enum MessageKind {
992 Simple,
994 Protected,
997 Tracked,
1000 Bouncing,
1002}
1003
1004impl Display for MessageKind {
1005 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1006 match self {
1007 MessageKind::Simple => write!(f, "Simple"),
1008 MessageKind::Protected => write!(f, "Protected"),
1009 MessageKind::Tracked => write!(f, "Tracked"),
1010 MessageKind::Bouncing => write!(f, "Bouncing"),
1011 }
1012 }
1013}
1014
1015#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, SimpleObject, Allocative)]
1017pub struct OutgoingMessage {
1018 pub destination: ChainId,
1020 #[debug(skip_if = Option::is_none)]
1022 pub authenticated_signer: Option<AccountOwner>,
1023 #[debug(skip_if = Amount::is_zero)]
1025 pub grant: Amount,
1026 #[debug(skip_if = Option::is_none)]
1028 pub refund_grant_to: Option<Account>,
1029 pub kind: MessageKind,
1031 pub message: Message,
1033}
1034
1035impl BcsHashable<'_> for OutgoingMessage {}
1036
1037impl OutgoingMessage {
1038 pub fn new(recipient: ChainId, message: impl Into<Message>) -> Self {
1040 OutgoingMessage {
1041 destination: recipient,
1042 authenticated_signer: None,
1043 grant: Amount::ZERO,
1044 refund_grant_to: None,
1045 kind: MessageKind::Simple,
1046 message: message.into(),
1047 }
1048 }
1049
1050 pub fn with_kind(mut self, kind: MessageKind) -> Self {
1052 self.kind = kind;
1053 self
1054 }
1055
1056 pub fn with_authenticated_signer(mut self, authenticated_signer: Option<AccountOwner>) -> Self {
1058 self.authenticated_signer = authenticated_signer;
1059 self
1060 }
1061}
1062
1063impl OperationContext {
1064 fn refund_grant_to(&self) -> Option<Account> {
1067 self.authenticated_signer.map(|owner| Account {
1068 chain_id: self.chain_id,
1069 owner,
1070 })
1071 }
1072}
1073
1074#[cfg(with_testing)]
1075#[derive(Clone)]
1076pub struct TestExecutionRuntimeContext {
1077 chain_id: ChainId,
1078 execution_runtime_config: ExecutionRuntimeConfig,
1079 user_contracts: Arc<papaya::HashMap<ApplicationId, UserContractCode>>,
1080 user_services: Arc<papaya::HashMap<ApplicationId, UserServiceCode>>,
1081 blobs: Arc<papaya::HashMap<BlobId, Blob>>,
1082 events: Arc<papaya::HashMap<EventId, Vec<u8>>>,
1083}
1084
1085#[cfg(with_testing)]
1086impl TestExecutionRuntimeContext {
1087 pub fn new(chain_id: ChainId, execution_runtime_config: ExecutionRuntimeConfig) -> Self {
1088 Self {
1089 chain_id,
1090 execution_runtime_config,
1091 user_contracts: Arc::default(),
1092 user_services: Arc::default(),
1093 blobs: Arc::default(),
1094 events: Arc::default(),
1095 }
1096 }
1097}
1098
1099#[cfg(with_testing)]
1100#[cfg_attr(not(web), async_trait)]
1101#[cfg_attr(web, async_trait(?Send))]
1102impl ExecutionRuntimeContext for TestExecutionRuntimeContext {
1103 fn chain_id(&self) -> ChainId {
1104 self.chain_id
1105 }
1106
1107 fn execution_runtime_config(&self) -> ExecutionRuntimeConfig {
1108 self.execution_runtime_config
1109 }
1110
1111 fn user_contracts(&self) -> &Arc<papaya::HashMap<ApplicationId, UserContractCode>> {
1112 &self.user_contracts
1113 }
1114
1115 fn user_services(&self) -> &Arc<papaya::HashMap<ApplicationId, UserServiceCode>> {
1116 &self.user_services
1117 }
1118
1119 async fn get_user_contract(
1120 &self,
1121 description: &ApplicationDescription,
1122 _txn_tracker: &TransactionTracker,
1123 ) -> Result<UserContractCode, ExecutionError> {
1124 let application_id: ApplicationId = description.into();
1125 let pinned = self.user_contracts().pin();
1126 Ok(pinned
1127 .get(&application_id)
1128 .ok_or_else(|| {
1129 ExecutionError::ApplicationBytecodeNotFound(Box::new(description.clone()))
1130 })?
1131 .clone())
1132 }
1133
1134 async fn get_user_service(
1135 &self,
1136 description: &ApplicationDescription,
1137 _txn_tracker: &TransactionTracker,
1138 ) -> Result<UserServiceCode, ExecutionError> {
1139 let application_id: ApplicationId = description.into();
1140 let pinned = self.user_services().pin();
1141 Ok(pinned
1142 .get(&application_id)
1143 .ok_or_else(|| {
1144 ExecutionError::ApplicationBytecodeNotFound(Box::new(description.clone()))
1145 })?
1146 .clone())
1147 }
1148
1149 async fn get_blob(&self, blob_id: BlobId) -> Result<Option<Blob>, ViewError> {
1150 Ok(self.blobs.pin().get(&blob_id).cloned())
1151 }
1152
1153 async fn get_event(&self, event_id: EventId) -> Result<Option<Vec<u8>>, ViewError> {
1154 Ok(self.events.pin().get(&event_id).cloned())
1155 }
1156
1157 async fn get_network_description(&self) -> Result<Option<NetworkDescription>, ViewError> {
1158 Ok(Some(NetworkDescription {
1159 admin_chain_id: dummy_chain_description(0).id(),
1160 genesis_config_hash: CryptoHash::test_hash("genesis config"),
1161 genesis_timestamp: Timestamp::from(0),
1162 genesis_committee_blob_hash: CryptoHash::test_hash("genesis committee"),
1163 name: "dummy network description".to_string(),
1164 }))
1165 }
1166
1167 async fn committees_for(
1168 &self,
1169 epoch_range: RangeInclusive<Epoch>,
1170 ) -> Result<BTreeMap<Epoch, Committee>, ViewError> {
1171 let pinned = self.blobs.pin();
1172 let committee_blob_bytes = pinned
1173 .values()
1174 .find(|blob| blob.content().blob_type() == BlobType::Committee)
1175 .ok_or_else(|| ViewError::NotFound("committee not found".to_owned()))?
1176 .bytes()
1177 .to_vec();
1178 let committee: Committee = bcs::from_bytes(&committee_blob_bytes)?;
1179 Ok((epoch_range.start().0..=epoch_range.end().0)
1183 .map(|epoch| (Epoch::from(epoch), committee.clone()))
1184 .collect())
1185 }
1186
1187 async fn contains_blob(&self, blob_id: BlobId) -> Result<bool, ViewError> {
1188 Ok(self.blobs.pin().contains_key(&blob_id))
1189 }
1190
1191 async fn contains_event(&self, event_id: EventId) -> Result<bool, ViewError> {
1192 Ok(self.events.pin().contains_key(&event_id))
1193 }
1194
1195 #[cfg(with_testing)]
1196 async fn add_blobs(
1197 &self,
1198 blobs: impl IntoIterator<Item = Blob> + Send,
1199 ) -> Result<(), ViewError> {
1200 let pinned = self.blobs.pin();
1201 for blob in blobs {
1202 pinned.insert(blob.id(), blob);
1203 }
1204
1205 Ok(())
1206 }
1207
1208 #[cfg(with_testing)]
1209 async fn add_events(
1210 &self,
1211 events: impl IntoIterator<Item = (EventId, Vec<u8>)> + Send,
1212 ) -> Result<(), ViewError> {
1213 let pinned = self.events.pin();
1214 for (event_id, bytes) in events {
1215 pinned.insert(event_id, bytes);
1216 }
1217
1218 Ok(())
1219 }
1220}
1221
1222impl From<SystemOperation> for Operation {
1223 fn from(operation: SystemOperation) -> Self {
1224 Operation::System(Box::new(operation))
1225 }
1226}
1227
1228impl Operation {
1229 pub fn system(operation: SystemOperation) -> Self {
1230 Operation::System(Box::new(operation))
1231 }
1232
1233 #[cfg(with_testing)]
1235 pub fn user<A: Abi>(
1236 application_id: ApplicationId<A>,
1237 operation: &A::Operation,
1238 ) -> Result<Self, bcs::Error> {
1239 Self::user_without_abi(application_id.forget_abi(), operation)
1240 }
1241
1242 #[cfg(with_testing)]
1245 pub fn user_without_abi(
1246 application_id: ApplicationId,
1247 operation: &impl Serialize,
1248 ) -> Result<Self, bcs::Error> {
1249 Ok(Operation::User {
1250 application_id,
1251 bytes: bcs::to_bytes(&operation)?,
1252 })
1253 }
1254
1255 pub fn as_system_operation(&self) -> Option<&SystemOperation> {
1258 match self {
1259 Operation::System(system_operation) => Some(system_operation),
1260 Operation::User { .. } => None,
1261 }
1262 }
1263
1264 pub fn application_id(&self) -> GenericApplicationId {
1265 match self {
1266 Self::System(_) => GenericApplicationId::System,
1267 Self::User { application_id, .. } => GenericApplicationId::User(*application_id),
1268 }
1269 }
1270
1271 pub fn published_blob_ids(&self) -> Vec<BlobId> {
1273 match self.as_system_operation() {
1274 Some(SystemOperation::PublishDataBlob { blob_hash }) => {
1275 vec![BlobId::new(*blob_hash, BlobType::Data)]
1276 }
1277 Some(SystemOperation::Admin(AdminOperation::PublishCommitteeBlob { blob_hash })) => {
1278 vec![BlobId::new(*blob_hash, BlobType::Committee)]
1279 }
1280 Some(SystemOperation::PublishModule { module_id }) => module_id.bytecode_blob_ids(),
1281 _ => vec![],
1282 }
1283 }
1284
1285 pub fn is_exempt_from_permissions(&self) -> bool {
1287 let Operation::System(system_op) = self else {
1288 return false;
1289 };
1290 matches!(
1291 **system_op,
1292 SystemOperation::ProcessNewEpoch(_)
1293 | SystemOperation::ProcessRemovedEpoch(_)
1294 | SystemOperation::UpdateStreams(_)
1295 )
1296 }
1297}
1298
1299impl From<SystemMessage> for Message {
1300 fn from(message: SystemMessage) -> Self {
1301 Message::System(message)
1302 }
1303}
1304
1305impl Message {
1306 pub fn system(message: SystemMessage) -> Self {
1307 Message::System(message)
1308 }
1309
1310 pub fn user<A, M: Serialize>(
1313 application_id: ApplicationId<A>,
1314 message: &M,
1315 ) -> Result<Self, bcs::Error> {
1316 let application_id = application_id.forget_abi();
1317 let bytes = bcs::to_bytes(&message)?;
1318 Ok(Message::User {
1319 application_id,
1320 bytes,
1321 })
1322 }
1323
1324 pub fn application_id(&self) -> GenericApplicationId {
1325 match self {
1326 Self::System(_) => GenericApplicationId::System,
1327 Self::User { application_id, .. } => GenericApplicationId::User(*application_id),
1328 }
1329 }
1330}
1331
1332impl From<SystemQuery> for Query {
1333 fn from(query: SystemQuery) -> Self {
1334 Query::System(query)
1335 }
1336}
1337
1338impl Query {
1339 pub fn system(query: SystemQuery) -> Self {
1340 Query::System(query)
1341 }
1342
1343 pub fn user<A: Abi>(
1345 application_id: ApplicationId<A>,
1346 query: &A::Query,
1347 ) -> Result<Self, serde_json::Error> {
1348 Self::user_without_abi(application_id.forget_abi(), query)
1349 }
1350
1351 pub fn user_without_abi(
1354 application_id: ApplicationId,
1355 query: &impl Serialize,
1356 ) -> Result<Self, serde_json::Error> {
1357 Ok(Query::User {
1358 application_id,
1359 bytes: serde_json::to_vec(&query)?,
1360 })
1361 }
1362
1363 pub fn application_id(&self) -> GenericApplicationId {
1364 match self {
1365 Self::System(_) => GenericApplicationId::System,
1366 Self::User { application_id, .. } => GenericApplicationId::User(*application_id),
1367 }
1368 }
1369}
1370
1371impl From<SystemResponse> for QueryResponse {
1372 fn from(response: SystemResponse) -> Self {
1373 QueryResponse::System(response)
1374 }
1375}
1376
1377impl From<Vec<u8>> for QueryResponse {
1378 fn from(response: Vec<u8>) -> Self {
1379 QueryResponse::User(response)
1380 }
1381}
1382
1383#[derive(Eq, PartialEq, Debug, Hash, Clone, Serialize, Deserialize)]
1385pub struct BlobState {
1386 pub last_used_by: Option<CryptoHash>,
1390 pub chain_id: ChainId,
1392 pub block_height: BlockHeight,
1394 pub epoch: Option<Epoch>,
1396}
1397
1398#[derive(Clone, Copy, Display)]
1400#[cfg_attr(with_wasm_runtime, derive(Debug, Default))]
1401pub enum WasmRuntime {
1402 #[cfg(with_wasmer)]
1403 #[default]
1404 #[display("wasmer")]
1405 Wasmer,
1406 #[cfg(with_wasmtime)]
1407 #[cfg_attr(not(with_wasmer), default)]
1408 #[display("wasmtime")]
1409 Wasmtime,
1410}
1411
1412#[derive(Clone, Copy, Display)]
1413#[cfg_attr(with_revm, derive(Debug, Default))]
1414pub enum EvmRuntime {
1415 #[cfg(with_revm)]
1416 #[default]
1417 #[display("revm")]
1418 Revm,
1419}
1420
1421pub trait WithWasmDefault {
1423 fn with_wasm_default(self) -> Self;
1424}
1425
1426impl WithWasmDefault for Option<WasmRuntime> {
1427 fn with_wasm_default(self) -> Self {
1428 #[cfg(with_wasm_runtime)]
1429 {
1430 Some(self.unwrap_or_default())
1431 }
1432 #[cfg(not(with_wasm_runtime))]
1433 {
1434 None
1435 }
1436 }
1437}
1438
1439impl FromStr for WasmRuntime {
1440 type Err = InvalidWasmRuntime;
1441
1442 fn from_str(string: &str) -> Result<Self, Self::Err> {
1443 match string {
1444 #[cfg(with_wasmer)]
1445 "wasmer" => Ok(WasmRuntime::Wasmer),
1446 #[cfg(with_wasmtime)]
1447 "wasmtime" => Ok(WasmRuntime::Wasmtime),
1448 unknown => Err(InvalidWasmRuntime(unknown.to_owned())),
1449 }
1450 }
1451}
1452
1453#[derive(Clone, Debug, Error)]
1455#[error("{0:?} is not a valid WebAssembly runtime")]
1456pub struct InvalidWasmRuntime(String);
1457
1458doc_scalar!(Operation, "An operation to be executed in a block");
1459doc_scalar!(
1460 Message,
1461 "A message to be sent and possibly executed in the receiver's block."
1462);
1463doc_scalar!(MessageKind, "The kind of outgoing message being sent");