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