mod applications;
pub mod committee;
mod execution;
mod execution_state_actor;
mod graphql;
mod ownership;
pub mod policy;
mod resources;
mod runtime;
pub mod system;
mod util;
mod wasm;
pub use crate::runtime::{ContractSyncRuntime, ServiceSyncRuntime};
pub use applications::{
ApplicationRegistryView, BytecodeLocation, GenericApplicationId, UserApplicationDescription,
UserApplicationId,
};
pub use execution::ExecutionStateView;
pub use ownership::{ChainOwnership, TimeoutConfig};
pub use resources::{ResourceController, ResourceTracker};
pub use system::{
SystemExecutionError, SystemExecutionStateView, SystemMessage, SystemOperation, SystemQuery,
SystemResponse,
};
#[cfg(all(
any(test, feature = "test"),
any(feature = "wasmer", feature = "wasmtime")
))]
pub use wasm::test as wasm_test;
#[cfg(any(feature = "wasmer", feature = "wasmtime"))]
pub use wasm::{WasmContractModule, WasmExecutionError, WasmServiceModule};
#[cfg(any(test, feature = "test"))]
pub use {applications::ApplicationRegistry, system::SystemExecutionState};
use async_graphql::SimpleObject;
use async_trait::async_trait;
use custom_debug_derive::Debug;
use dashmap::DashMap;
use derive_more::Display;
use linera_base::{
abi::Abi,
crypto::CryptoHash,
data_types::{Amount, ArithmeticError, BlockHeight, Timestamp},
doc_scalar, hex_debug,
identifiers::{BytecodeId, ChainId, ChannelName, Destination, MessageId, Owner, SessionId},
};
use linera_views::{batch::Batch, views::ViewError};
use serde::{Deserialize, Serialize};
use std::{fmt, io, path::Path, str::FromStr, sync::Arc};
use thiserror::Error;
pub type UserContractCode = Arc<dyn UserContractModule + Send + Sync + 'static>;
pub type UserServiceCode = Arc<dyn UserServiceModule + Send + Sync + 'static>;
pub type UserContractInstance = Box<dyn UserContract + Send + Sync + 'static>;
pub type UserServiceInstance = Box<dyn UserService + Send + Sync + 'static>;
pub trait UserContractModule {
fn instantiate(
&self,
runtime: ContractSyncRuntime,
) -> Result<UserContractInstance, ExecutionError>;
}
pub trait UserServiceModule {
fn instantiate(
&self,
runtime: ServiceSyncRuntime,
) -> Result<UserServiceInstance, ExecutionError>;
}
#[derive(Error, Debug)]
pub enum ExecutionError {
#[error(transparent)]
ViewError(#[from] ViewError),
#[error(transparent)]
ArithmeticError(#[from] ArithmeticError),
#[error(transparent)]
SystemError(#[from] SystemExecutionError),
#[error("User application reported an error: {0}")]
UserError(String),
#[cfg(any(feature = "wasmer", feature = "wasmtime"))]
#[error(transparent)]
WasmError(#[from] WasmExecutionError),
#[error(transparent)]
JoinError(#[from] tokio::task::JoinError),
#[error("The given promise is invalid or was polled once already")]
InvalidPromise,
#[error("Session {0} does not exist or was already closed")]
InvalidSession(SessionId),
#[error("Attempted to call or forward an active session {0}")]
SessionIsInUse(SessionId),
#[error("Attempted to save a session {0} but it is not locked")]
SessionStateNotLocked(SessionId),
#[error("Session {session_id} is owned by {owned_by} but was accessed by {accessed_by}")]
InvalidSessionOwner {
session_id: Box<SessionId>,
accessed_by: Box<UserApplicationId>,
owned_by: Box<UserApplicationId>,
},
#[error("Session {0} is still opened at the end of a transaction")]
SessionWasNotClosed(SessionId),
#[error("Attempted to perform a reentrant call to application {0}")]
ReentrantCall(UserApplicationId),
#[error("Failed to load bytecode from storage {0:?}")]
ApplicationBytecodeNotFound(Box<UserApplicationDescription>),
#[error("Excessive number of bytes read from storage")]
ExcessiveRead,
#[error("Excessive number of bytes written to storage")]
ExcessiveWrite,
#[error("Runtime failed to respond to application")]
MissingRuntimeResponse,
#[error("Bytecode ID {0:?} is invalid")]
InvalidBytecodeId(BytecodeId),
}
impl ExecutionError {
fn invalid_session_owner(
session_id: SessionId,
accessed_by: UserApplicationId,
owned_by: UserApplicationId,
) -> Self {
Self::InvalidSessionOwner {
session_id: Box::new(session_id),
accessed_by: Box::new(accessed_by),
owned_by: Box::new(owned_by),
}
}
}
pub trait UserContract {
fn initialize(
&mut self,
context: OperationContext,
argument: Vec<u8>,
) -> Result<RawExecutionOutcome<Vec<u8>>, ExecutionError>;
fn execute_operation(
&mut self,
context: OperationContext,
operation: Vec<u8>,
) -> Result<RawExecutionOutcome<Vec<u8>>, ExecutionError>;
fn execute_message(
&mut self,
context: MessageContext,
message: Vec<u8>,
) -> Result<RawExecutionOutcome<Vec<u8>>, ExecutionError>;
fn handle_application_call(
&mut self,
context: CalleeContext,
argument: Vec<u8>,
forwarded_sessions: Vec<SessionId>,
) -> Result<ApplicationCallOutcome, ExecutionError>;
fn handle_session_call(
&mut self,
context: CalleeContext,
session_state: Vec<u8>,
argument: Vec<u8>,
forwarded_sessions: Vec<SessionId>,
) -> Result<(SessionCallOutcome, Vec<u8>), ExecutionError>;
}
pub trait UserService {
fn handle_query(
&mut self,
context: QueryContext,
argument: Vec<u8>,
) -> Result<Vec<u8>, ExecutionError>;
}
#[derive(Default)]
pub struct ApplicationCallOutcome {
pub value: Vec<u8>,
pub execution_outcome: RawExecutionOutcome<Vec<u8>>,
pub create_sessions: Vec<Vec<u8>>,
}
impl ApplicationCallOutcome {
pub fn with_message(mut self, message: RawOutgoingMessage<Vec<u8>>) -> Self {
self.execution_outcome.messages.push(message);
self
}
pub fn with_new_session(mut self, session_state: Vec<u8>) -> Self {
self.create_sessions.push(session_state);
self
}
}
#[derive(Default)]
pub struct SessionCallOutcome {
pub inner: ApplicationCallOutcome,
pub close_session: bool,
}
#[derive(Default, Clone, Copy)]
pub enum ExecutionRuntimeConfig {
#[default]
Synchronous,
}
#[async_trait]
pub trait ExecutionRuntimeContext {
fn chain_id(&self) -> ChainId;
fn execution_runtime_config(&self) -> ExecutionRuntimeConfig;
fn user_contracts(&self) -> &Arc<DashMap<UserApplicationId, UserContractCode>>;
fn user_services(&self) -> &Arc<DashMap<UserApplicationId, UserServiceCode>>;
async fn get_user_contract(
&self,
description: &UserApplicationDescription,
) -> Result<UserContractCode, ExecutionError>;
async fn get_user_service(
&self,
description: &UserApplicationDescription,
) -> Result<UserServiceCode, ExecutionError>;
}
#[derive(Clone, Copy, Debug)]
pub struct OperationContext {
pub chain_id: ChainId,
pub authenticated_signer: Option<Owner>,
pub height: BlockHeight,
pub index: u32,
pub next_message_index: u32,
}
#[derive(Clone, Copy, Debug)]
pub struct MessageContext {
pub chain_id: ChainId,
pub is_bouncing: bool,
pub authenticated_signer: Option<Owner>,
pub height: BlockHeight,
pub certificate_hash: CryptoHash,
pub message_id: MessageId,
}
#[derive(Clone, Copy, Debug)]
pub struct CalleeContext {
pub chain_id: ChainId,
pub authenticated_signer: Option<Owner>,
pub authenticated_caller_id: Option<UserApplicationId>,
}
#[derive(Clone, Copy, Debug)]
pub struct QueryContext {
pub chain_id: ChainId,
}
pub trait BaseRuntime {
type Read: fmt::Debug + Send;
type ContainsKey: fmt::Debug + Send;
type ReadMultiValuesBytes: fmt::Debug + Send;
type ReadValueBytes: fmt::Debug + Send;
type FindKeysByPrefix: fmt::Debug + Send;
type FindKeyValuesByPrefix: fmt::Debug + Send;
fn chain_id(&mut self) -> Result<ChainId, ExecutionError>;
fn application_id(&mut self) -> Result<UserApplicationId, ExecutionError>;
fn application_parameters(&mut self) -> Result<Vec<u8>, ExecutionError>;
fn read_system_balance(&mut self) -> Result<Amount, ExecutionError>;
fn read_system_timestamp(&mut self) -> Result<Timestamp, ExecutionError>;
#[cfg(feature = "test")]
fn contains_key(&mut self, key: Vec<u8>) -> Result<bool, ExecutionError> {
let promise = self.contains_key_new(key)?;
self.contains_key_wait(&promise)
}
fn contains_key_new(&mut self, key: Vec<u8>) -> Result<Self::ContainsKey, ExecutionError>;
fn contains_key_wait(&mut self, promise: &Self::ContainsKey) -> Result<bool, ExecutionError>;
#[cfg(feature = "test")]
fn read_multi_values_bytes(
&mut self,
keys: Vec<Vec<u8>>,
) -> Result<Vec<Option<Vec<u8>>>, ExecutionError> {
let promise = self.read_multi_values_bytes_new(keys)?;
self.read_multi_values_bytes_wait(&promise)
}
fn read_multi_values_bytes_new(
&mut self,
keys: Vec<Vec<u8>>,
) -> Result<Self::ReadMultiValuesBytes, ExecutionError>;
fn read_multi_values_bytes_wait(
&mut self,
promise: &Self::ReadMultiValuesBytes,
) -> Result<Vec<Option<Vec<u8>>>, ExecutionError>;
fn write_batch(&mut self, batch: Batch) -> Result<(), ExecutionError>;
#[cfg(feature = "test")]
fn read_value_bytes(&mut self, key: Vec<u8>) -> Result<Option<Vec<u8>>, ExecutionError> {
let promise = self.read_value_bytes_new(key)?;
self.read_value_bytes_wait(&promise)
}
fn read_value_bytes_new(
&mut self,
key: Vec<u8>,
) -> Result<Self::ReadValueBytes, ExecutionError>;
fn read_value_bytes_wait(
&mut self,
promise: &Self::ReadValueBytes,
) -> Result<Option<Vec<u8>>, ExecutionError>;
fn find_keys_by_prefix_new(
&mut self,
key_prefix: Vec<u8>,
) -> Result<Self::FindKeysByPrefix, ExecutionError>;
fn find_keys_by_prefix_wait(
&mut self,
promise: &Self::FindKeysByPrefix,
) -> Result<Vec<Vec<u8>>, ExecutionError>;
#[cfg(feature = "test")]
#[allow(clippy::type_complexity)]
fn find_key_values_by_prefix(
&mut self,
key_prefix: Vec<u8>,
) -> Result<Vec<(Vec<u8>, Vec<u8>)>, ExecutionError> {
let promise = self.find_key_values_by_prefix_new(key_prefix)?;
self.find_key_values_by_prefix_wait(&promise)
}
fn find_key_values_by_prefix_new(
&mut self,
key_prefix: Vec<u8>,
) -> Result<Self::FindKeyValuesByPrefix, ExecutionError>;
#[allow(clippy::type_complexity)]
fn find_key_values_by_prefix_wait(
&mut self,
promise: &Self::FindKeyValuesByPrefix,
) -> Result<Vec<(Vec<u8>, Vec<u8>)>, ExecutionError>;
}
pub trait ServiceRuntime: BaseRuntime {
fn try_query_application(
&mut self,
queried_id: UserApplicationId,
argument: Vec<u8>,
) -> Result<Vec<u8>, ExecutionError>;
}
pub struct CallOutcome {
pub value: Vec<u8>,
pub sessions: Vec<SessionId>,
}
pub trait ContractRuntime: BaseRuntime {
fn remaining_fuel(&mut self) -> Result<u64, ExecutionError>;
fn set_remaining_fuel(&mut self, remaining_fuel: u64) -> Result<(), ExecutionError>;
fn try_call_application(
&mut self,
authenticated: bool,
callee_id: UserApplicationId,
argument: Vec<u8>,
forwarded_sessions: Vec<SessionId>,
) -> Result<CallOutcome, ExecutionError>;
fn try_call_session(
&mut self,
authenticated: bool,
session_id: SessionId,
argument: Vec<u8>,
forwarded_sessions: Vec<SessionId>,
) -> Result<CallOutcome, ExecutionError>;
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
pub enum Operation {
System(SystemOperation),
User {
application_id: UserApplicationId,
#[serde(with = "serde_bytes")]
#[debug(with = "hex_debug")]
bytes: Vec<u8>,
},
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
pub enum Message {
System(SystemMessage),
User {
application_id: UserApplicationId,
#[serde(with = "serde_bytes")]
#[debug(with = "hex_debug")]
bytes: Vec<u8>,
},
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
pub enum Query {
System(SystemQuery),
User {
application_id: UserApplicationId,
#[serde(with = "serde_bytes")]
#[debug(with = "hex_debug")]
bytes: Vec<u8>,
},
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
pub enum Response {
System(SystemResponse),
User(
#[serde(with = "serde_bytes")]
#[debug(with = "hex_debug")]
Vec<u8>,
),
}
#[derive(Clone, Debug)]
#[cfg_attr(any(test, feature = "test"), derive(Eq, PartialEq))]
pub struct RawOutgoingMessage<Message> {
pub destination: Destination,
pub authenticated: bool,
pub kind: MessageKind,
pub message: Message,
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, Copy)]
pub enum MessageKind {
Simple,
Protected,
Tracked,
Bouncing,
}
#[derive(Debug)]
#[cfg_attr(any(test, feature = "test"), derive(Eq, PartialEq))]
pub struct RawExecutionOutcome<Message> {
pub authenticated_signer: Option<Owner>,
pub messages: Vec<RawOutgoingMessage<Message>>,
pub subscribe: Vec<(ChannelName, ChainId)>,
pub unsubscribe: Vec<(ChannelName, ChainId)>,
}
#[derive(
Eq, PartialEq, Ord, PartialOrd, Debug, Clone, Hash, Serialize, Deserialize, SimpleObject,
)]
pub struct ChannelSubscription {
pub chain_id: ChainId,
pub name: ChannelName,
}
#[derive(Debug)]
#[cfg_attr(any(test, feature = "test"), derive(Eq, PartialEq))]
#[allow(clippy::large_enum_variant)]
pub enum ExecutionOutcome {
System(RawExecutionOutcome<SystemMessage>),
User(UserApplicationId, RawExecutionOutcome<Vec<u8>>),
}
impl ExecutionOutcome {
pub fn application_id(&self) -> GenericApplicationId {
match self {
ExecutionOutcome::System(_) => GenericApplicationId::System,
ExecutionOutcome::User(app_id, _) => GenericApplicationId::User(*app_id),
}
}
}
impl<Message> RawExecutionOutcome<Message> {
pub fn with_authenticated_signer(mut self, authenticated_signer: Option<Owner>) -> Self {
self.authenticated_signer = authenticated_signer;
self
}
pub fn with_message(mut self, message: RawOutgoingMessage<Message>) -> Self {
self.messages.push(message);
self
}
}
impl<Message> Default for RawExecutionOutcome<Message> {
fn default() -> Self {
Self {
authenticated_signer: None,
messages: Vec::new(),
subscribe: Vec::new(),
unsubscribe: Vec::new(),
}
}
}
impl OperationContext {
fn next_message_id(&self) -> MessageId {
MessageId {
chain_id: self.chain_id,
height: self.height,
index: self.next_message_index,
}
}
}
#[cfg(any(test, feature = "test"))]
#[derive(Clone)]
pub struct TestExecutionRuntimeContext {
chain_id: ChainId,
execution_runtime_config: ExecutionRuntimeConfig,
user_contracts: Arc<DashMap<UserApplicationId, UserContractCode>>,
user_services: Arc<DashMap<UserApplicationId, UserServiceCode>>,
}
#[cfg(any(test, feature = "test"))]
impl TestExecutionRuntimeContext {
fn new(chain_id: ChainId, execution_runtime_config: ExecutionRuntimeConfig) -> Self {
Self {
chain_id,
execution_runtime_config,
user_contracts: Arc::default(),
user_services: Arc::default(),
}
}
}
#[cfg(any(test, feature = "test"))]
#[async_trait]
impl ExecutionRuntimeContext for TestExecutionRuntimeContext {
fn chain_id(&self) -> ChainId {
self.chain_id
}
fn execution_runtime_config(&self) -> ExecutionRuntimeConfig {
self.execution_runtime_config
}
fn user_contracts(&self) -> &Arc<DashMap<UserApplicationId, UserContractCode>> {
&self.user_contracts
}
fn user_services(&self) -> &Arc<DashMap<UserApplicationId, UserServiceCode>> {
&self.user_services
}
async fn get_user_contract(
&self,
description: &UserApplicationDescription,
) -> Result<UserContractCode, ExecutionError> {
let application_id = description.into();
Ok(self
.user_contracts()
.get(&application_id)
.ok_or_else(|| {
ExecutionError::ApplicationBytecodeNotFound(Box::new(description.clone()))
})?
.clone())
}
async fn get_user_service(
&self,
description: &UserApplicationDescription,
) -> Result<UserServiceCode, ExecutionError> {
let application_id = description.into();
Ok(self
.user_services()
.get(&application_id)
.ok_or_else(|| {
ExecutionError::ApplicationBytecodeNotFound(Box::new(description.clone()))
})?
.clone())
}
}
impl From<SystemOperation> for Operation {
fn from(operation: SystemOperation) -> Self {
Operation::System(operation)
}
}
impl Operation {
pub fn system(operation: SystemOperation) -> Self {
Operation::System(operation)
}
pub fn user<A: Abi>(
application_id: UserApplicationId<A>,
operation: &A::Operation,
) -> Result<Self, bcs::Error> {
let application_id = application_id.forget_abi();
let bytes = bcs::to_bytes(&operation)?;
Ok(Operation::User {
application_id,
bytes,
})
}
pub fn application_id(&self) -> GenericApplicationId {
match self {
Self::System(_) => GenericApplicationId::System,
Self::User { application_id, .. } => GenericApplicationId::User(*application_id),
}
}
}
impl From<SystemMessage> for Message {
fn from(message: SystemMessage) -> Self {
Message::System(message)
}
}
impl Message {
pub fn system(message: SystemMessage) -> Self {
Message::System(message)
}
pub fn user<A: Abi>(
application_id: UserApplicationId<A>,
message: &A::Message,
) -> Result<Self, bcs::Error> {
let application_id = application_id.forget_abi();
let bytes = bcs::to_bytes(&message)?;
Ok(Message::User {
application_id,
bytes,
})
}
pub fn application_id(&self) -> GenericApplicationId {
match self {
Self::System(_) => GenericApplicationId::System,
Self::User { application_id, .. } => GenericApplicationId::User(*application_id),
}
}
}
impl From<SystemQuery> for Query {
fn from(query: SystemQuery) -> Self {
Query::System(query)
}
}
impl Query {
pub fn system(query: SystemQuery) -> Self {
Query::System(query)
}
pub fn user<A: Abi>(
application_id: UserApplicationId<A>,
query: &A::Query,
) -> Result<Self, serde_json::Error> {
let application_id = application_id.forget_abi();
let bytes = serde_json::to_vec(&query)?;
Ok(Query::User {
application_id,
bytes,
})
}
pub fn application_id(&self) -> GenericApplicationId {
match self {
Self::System(_) => GenericApplicationId::System,
Self::User { application_id, .. } => GenericApplicationId::User(*application_id),
}
}
}
impl From<SystemResponse> for Response {
fn from(response: SystemResponse) -> Self {
Response::System(response)
}
}
impl From<Vec<u8>> for Response {
fn from(response: Vec<u8>) -> Self {
Response::User(response)
}
}
#[derive(Clone, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub struct Bytecode {
#[serde(with = "serde_bytes")]
bytes: Vec<u8>,
}
impl Bytecode {
#[cfg(any(feature = "wasmer", feature = "wasmtime"))]
pub(crate) fn new(bytes: Vec<u8>) -> Self {
Bytecode { bytes }
}
pub async fn load_from_file(path: impl AsRef<Path>) -> Result<Self, io::Error> {
let bytes = tokio::fs::read(path).await?;
Ok(Bytecode { bytes })
}
}
impl AsRef<[u8]> for Bytecode {
fn as_ref(&self) -> &[u8] {
self.bytes.as_ref()
}
}
impl std::fmt::Debug for Bytecode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
f.debug_tuple("Bytecode").finish()
}
}
#[derive(Clone, Copy, Display)]
#[cfg_attr(any(feature = "wasmtime", feature = "wasmer"), derive(Debug, Default))]
pub enum WasmRuntime {
#[cfg(feature = "wasmer")]
#[default]
#[display(fmt = "wasmer")]
Wasmer,
#[cfg(feature = "wasmtime")]
#[cfg_attr(not(feature = "wasmer"), default)]
#[display(fmt = "wasmtime")]
Wasmtime,
#[cfg(feature = "wasmer")]
WasmerWithSanitizer,
#[cfg(feature = "wasmtime")]
WasmtimeWithSanitizer,
}
pub trait WithWasmDefault {
fn with_wasm_default(self) -> Self;
}
impl WasmRuntime {
#[cfg(any(feature = "wasmer", feature = "wasmtime"))]
pub fn default_with_sanitizer() -> Self {
#[cfg(feature = "wasmer")]
{
WasmRuntime::WasmerWithSanitizer
}
#[cfg(not(feature = "wasmer"))]
{
WasmRuntime::WasmtimeWithSanitizer
}
}
pub fn needs_sanitizer(self) -> bool {
match self {
#[cfg(feature = "wasmer")]
WasmRuntime::WasmerWithSanitizer => true,
#[cfg(feature = "wasmtime")]
WasmRuntime::WasmtimeWithSanitizer => true,
#[cfg(any(feature = "wasmtime", feature = "wasmer"))]
_ => false,
}
}
}
impl WithWasmDefault for Option<WasmRuntime> {
#[cfg(any(feature = "wasmer", feature = "wasmtime"))]
fn with_wasm_default(self) -> Self {
Some(self.unwrap_or_default())
}
#[cfg(not(any(feature = "wasmer", feature = "wasmtime")))]
fn with_wasm_default(self) -> Self {
None
}
}
impl FromStr for WasmRuntime {
type Err = InvalidWasmRuntime;
fn from_str(string: &str) -> Result<Self, Self::Err> {
match string {
#[cfg(feature = "wasmer")]
"wasmer" => Ok(WasmRuntime::Wasmer),
#[cfg(feature = "wasmtime")]
"wasmtime" => Ok(WasmRuntime::Wasmtime),
unknown => Err(InvalidWasmRuntime(unknown.to_owned())),
}
}
}
#[derive(Clone, Debug, Error)]
#[error("{0:?} is not a valid WebAssembly runtime")]
pub struct InvalidWasmRuntime(String);
doc_scalar!(Operation, "An operation to be executed in a block");
doc_scalar!(
Message,
"An message to be sent and possibly executed in the receiver's block."
);
doc_scalar!(MessageKind, "The kind of outgoing message being sent");