mod applications;
pub mod committee;
mod execution;
mod graphql;
mod ownership;
pub mod pricing;
mod runtime;
pub mod system;
mod wasm;
pub use applications::{
ApplicationId, ApplicationRegistryView, BytecodeLocation, UserApplicationDescription,
UserApplicationId,
};
pub use execution::ExecutionStateView;
pub use ownership::ChainOwnership;
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::{WasmApplication, WasmExecutionError};
#[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::{io, path::Path, str::FromStr, sync::Arc};
use thiserror::Error;
pub type UserApplicationCode = Arc<dyn UserApplication + Send + Sync + 'static>;
#[derive(Error, Debug)]
pub enum ExecutionError {
#[error(transparent)]
ViewError(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("A session is still opened at the end of a transaction")]
SessionWasNotClosed,
#[error("Invalid operation for this application")]
InvalidOperation,
#[error("Invalid message for this application")]
InvalidMessage,
#[error("Invalid query for this application")]
InvalidQuery,
#[error("Can't call another application during a query")]
CallApplicationFromQuery,
#[error("Queries can't change application state")]
LockStateFromQuery,
#[error("Session does not exist or was already closed")]
InvalidSession,
#[error("Attempted to call or forward an active session")]
SessionIsInUse,
#[error("Session is not accessible by this owner")]
InvalidSessionOwner,
#[error("Attempted to call an application while the state is locked")]
ApplicationIsInUse,
#[error("Attempted to get an entry that is not locked")]
ApplicationStateNotLocked,
#[error("Bytecode ID {0:?} is invalid")]
InvalidBytecodeId(BytecodeId),
#[error("Failed to load bytecode from storage {0:?}")]
ApplicationBytecodeNotFound(Box<UserApplicationDescription>),
}
impl From<ViewError> for ExecutionError {
fn from(error: ViewError) -> Self {
match error {
ViewError::TryLockError(_) => ExecutionError::ApplicationIsInUse,
error => ExecutionError::ViewError(error),
}
}
}
#[async_trait]
pub trait UserApplication {
async fn initialize(
&self,
context: &OperationContext,
runtime: &dyn ContractRuntime,
argument: &[u8],
) -> Result<RawExecutionResult<Vec<u8>>, ExecutionError>;
async fn execute_operation(
&self,
context: &OperationContext,
runtime: &dyn ContractRuntime,
operation: &[u8],
) -> Result<RawExecutionResult<Vec<u8>>, ExecutionError>;
async fn execute_message(
&self,
context: &MessageContext,
runtime: &dyn ContractRuntime,
message: &[u8],
) -> Result<RawExecutionResult<Vec<u8>>, ExecutionError>;
async fn handle_application_call(
&self,
context: &CalleeContext,
runtime: &dyn ContractRuntime,
argument: &[u8],
forwarded_sessions: Vec<SessionId>,
) -> Result<ApplicationCallResult, ExecutionError>;
async fn handle_session_call(
&self,
context: &CalleeContext,
runtime: &dyn ContractRuntime,
session_state: &mut Vec<u8>,
argument: &[u8],
forwarded_sessions: Vec<SessionId>,
) -> Result<SessionCallResult, ExecutionError>;
async fn query_application(
&self,
context: &QueryContext,
runtime: &dyn ServiceRuntime,
argument: &[u8],
) -> Result<Vec<u8>, ExecutionError>;
}
#[derive(Default)]
pub struct ApplicationCallResult {
pub value: Vec<u8>,
pub execution_result: RawExecutionResult<Vec<u8>>,
pub create_sessions: Vec<Vec<u8>>,
}
#[derive(Default)]
pub struct SessionCallResult {
pub inner: ApplicationCallResult,
pub close_session: bool,
}
#[async_trait]
pub trait ExecutionRuntimeContext {
fn chain_id(&self) -> ChainId;
fn user_applications(&self) -> &Arc<DashMap<UserApplicationId, UserApplicationCode>>;
async fn get_user_application(
&self,
description: &UserApplicationDescription,
) -> Result<UserApplicationCode, 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 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,
}
#[async_trait]
pub trait BaseRuntime: Send + Sync {
fn chain_id(&self) -> ChainId;
fn application_id(&self) -> UserApplicationId;
fn application_parameters(&self) -> Vec<u8>;
fn read_system_balance(&self) -> Amount;
fn read_system_timestamp(&self) -> Timestamp;
async fn try_read_my_state(&self) -> Result<Vec<u8>, ExecutionError>;
async fn lock_view_user_state(&self) -> Result<(), ExecutionError>;
async fn unlock_view_user_state(&self) -> Result<(), ExecutionError>;
async fn read_key_bytes(&self, key: Vec<u8>) -> Result<Option<Vec<u8>>, ExecutionError>;
async fn find_keys_by_prefix(
&self,
key_prefix: Vec<u8>,
) -> Result<Vec<Vec<u8>>, ExecutionError>;
async fn find_key_values_by_prefix(
&self,
key_prefix: Vec<u8>,
) -> Result<Vec<(Vec<u8>, Vec<u8>)>, ExecutionError>;
}
#[async_trait]
pub trait ServiceRuntime: BaseRuntime {
async fn try_query_application(
&self,
queried_id: UserApplicationId,
argument: &[u8],
) -> Result<Vec<u8>, ExecutionError>;
}
pub struct CallResult {
pub value: Vec<u8>,
pub sessions: Vec<SessionId>,
}
#[async_trait]
pub trait ContractRuntime: BaseRuntime {
fn remaining_fuel(&self) -> u64;
fn set_remaining_fuel(&self, remaining_fuel: u64);
async fn try_read_and_lock_my_state(&self) -> Result<Vec<u8>, ExecutionError>;
fn save_and_unlock_my_state(&self, state: Vec<u8>) -> Result<(), ExecutionError>;
fn unlock_my_state(&self);
async fn write_batch_and_unlock(&self, batch: Batch) -> Result<(), ExecutionError>;
async fn try_call_application(
&self,
authenticated: bool,
callee_id: UserApplicationId,
argument: &[u8],
forwarded_sessions: Vec<SessionId>,
) -> Result<CallResult, ExecutionError>;
async fn try_call_session(
&self,
authenticated: bool,
session_id: SessionId,
argument: &[u8],
forwarded_sessions: Vec<SessionId>,
) -> Result<CallResult, 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(Debug)]
#[cfg_attr(any(test, feature = "test"), derive(Eq, PartialEq))]
pub struct RawExecutionResult<Message> {
pub authenticated_signer: Option<Owner>,
pub messages: Vec<(Destination, bool, 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 ExecutionResult {
System(RawExecutionResult<SystemMessage>),
User(UserApplicationId, RawExecutionResult<Vec<u8>>),
}
impl ExecutionResult {
pub fn application_id(&self) -> ApplicationId {
match self {
ExecutionResult::System(_) => ApplicationId::System,
ExecutionResult::User(app_id, _) => ApplicationId::User(*app_id),
}
}
}
impl<Message> RawExecutionResult<Message> {
pub fn with_authenticated_signer(mut self, authenticated_signer: Option<Owner>) -> Self {
self.authenticated_signer = authenticated_signer;
self
}
}
impl<Message> Default for RawExecutionResult<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,
user_applications: Arc<DashMap<UserApplicationId, UserApplicationCode>>,
}
#[cfg(any(test, feature = "test"))]
impl TestExecutionRuntimeContext {
fn new(chain_id: ChainId) -> Self {
Self {
chain_id,
user_applications: Arc::default(),
}
}
}
#[cfg(any(test, feature = "test"))]
#[async_trait]
impl ExecutionRuntimeContext for TestExecutionRuntimeContext {
fn chain_id(&self) -> ChainId {
self.chain_id
}
fn user_applications(&self) -> &Arc<DashMap<UserApplicationId, UserApplicationCode>> {
&self.user_applications
}
async fn get_user_application(
&self,
description: &UserApplicationDescription,
) -> Result<UserApplicationCode, ExecutionError> {
let application_id = description.into();
Ok(self
.user_applications()
.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) -> ApplicationId {
match self {
Self::System(_) => ApplicationId::System,
Self::User { application_id, .. } => ApplicationId::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) -> ApplicationId {
match self {
Self::System(_) => ApplicationId::System,
Self::User { application_id, .. } => ApplicationId::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) -> ApplicationId {
match self {
Self::System(_) => ApplicationId::System,
Self::User { application_id, .. } => ApplicationId::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."
);