use std::sync::Mutex;
use linera_base::{
abi::ServiceAbi,
data_types::{Amount, ApplicationDescription, BlockHeight, Timestamp},
http,
identifiers::{AccountOwner, ApplicationId, ChainId, DataBlobHash},
};
use serde::Serialize;
use super::wit::{base_runtime_api as base_wit, service_runtime_api as service_wit};
use crate::{KeyValueStore, Service, ViewStorageContext};
pub struct ServiceRuntime<Application>
where
Application: Service,
{
application_parameters: Mutex<Option<Application::Parameters>>,
application_id: Mutex<Option<ApplicationId<Application::Abi>>>,
application_creator_chain_id: Mutex<Option<ChainId>>,
chain_id: Mutex<Option<ChainId>>,
next_block_height: Mutex<Option<BlockHeight>>,
timestamp: Mutex<Option<Timestamp>>,
chain_balance: Mutex<Option<Amount>>,
owner_balances: Mutex<Option<Vec<(AccountOwner, Amount)>>>,
balance_owners: Mutex<Option<Vec<AccountOwner>>>,
}
impl<Application> ServiceRuntime<Application>
where
Application: Service,
{
pub(crate) fn new() -> Self {
ServiceRuntime {
application_parameters: Mutex::new(None),
application_id: Mutex::new(None),
application_creator_chain_id: Mutex::new(None),
chain_id: Mutex::new(None),
next_block_height: Mutex::new(None),
timestamp: Mutex::new(None),
chain_balance: Mutex::new(None),
owner_balances: Mutex::new(None),
balance_owners: Mutex::new(None),
}
}
pub fn key_value_store(&self) -> KeyValueStore {
KeyValueStore::for_services()
}
pub fn root_view_storage_context(&self) -> ViewStorageContext {
ViewStorageContext::new_unchecked(self.key_value_store(), Vec::new(), ())
}
}
impl<Application> ServiceRuntime<Application>
where
Application: Service,
{
pub fn application_parameters(&self) -> Application::Parameters {
Self::fetch_value_through_cache(&self.application_parameters, || {
let bytes = base_wit::application_parameters();
serde_json::from_slice(&bytes).expect("Application parameters must be deserializable")
})
}
pub fn application_id(&self) -> ApplicationId<Application::Abi> {
Self::fetch_value_through_cache(&self.application_id, || {
ApplicationId::from(base_wit::get_application_id()).with_abi()
})
}
pub fn application_creator_chain_id(&self) -> ChainId {
Self::fetch_value_through_cache(&self.application_creator_chain_id, || {
base_wit::get_application_creator_chain_id().into()
})
}
pub fn read_application_description(
&self,
application_id: ApplicationId,
) -> ApplicationDescription {
base_wit::read_application_description(application_id.forget_abi().into()).into()
}
pub fn chain_id(&self) -> ChainId {
Self::fetch_value_through_cache(&self.chain_id, || base_wit::get_chain_id().into())
}
pub fn next_block_height(&self) -> BlockHeight {
Self::fetch_value_through_cache(&self.next_block_height, || {
base_wit::get_block_height().into()
})
}
pub fn system_time(&self) -> Timestamp {
Self::fetch_value_through_cache(&self.timestamp, || {
base_wit::read_system_timestamp().into()
})
}
pub fn chain_balance(&self) -> Amount {
Self::fetch_value_through_cache(&self.chain_balance, || {
base_wit::read_chain_balance().into()
})
}
pub fn owner_balance(&self, owner: AccountOwner) -> Amount {
base_wit::read_owner_balance(owner.into()).into()
}
pub fn owner_balances(&self) -> Vec<(AccountOwner, Amount)> {
Self::fetch_value_through_cache(&self.owner_balances, || {
base_wit::read_owner_balances()
.into_iter()
.map(|(owner, amount)| (owner.into(), amount.into()))
.collect()
})
}
pub fn balance_owners(&self) -> Vec<AccountOwner> {
Self::fetch_value_through_cache(&self.balance_owners, || {
base_wit::read_balance_owners()
.into_iter()
.map(AccountOwner::from)
.collect()
})
}
pub fn http_request(&self, request: http::Request) -> http::Response {
base_wit::perform_http_request(&request.into()).into()
}
pub fn read_data_blob(&self, hash: DataBlobHash) -> Vec<u8> {
base_wit::read_data_blob(hash.into())
}
pub fn assert_data_blob_exists(&self, hash: DataBlobHash) {
base_wit::assert_data_blob_exists(hash.into())
}
}
impl<Application> ServiceRuntime<Application>
where
Application: Service,
{
pub fn schedule_raw_operation(&self, operation: Vec<u8>) {
service_wit::schedule_operation(&operation);
}
pub fn schedule_operation(&self, operation: &impl Serialize) {
let bytes = bcs::to_bytes(operation).expect("Failed to serialize application operation");
service_wit::schedule_operation(&bytes);
}
pub fn query_application<A: ServiceAbi>(
&self,
application: ApplicationId<A>,
query: &A::Query,
) -> A::QueryResponse {
let query_bytes =
serde_json::to_vec(&query).expect("Failed to serialize query to another application");
let response_bytes =
service_wit::try_query_application(application.forget_abi().into(), &query_bytes);
serde_json::from_slice(&response_bytes)
.expect("Failed to deserialize query response from application")
}
}
impl<Application> ServiceRuntime<Application>
where
Application: Service,
{
fn fetch_value_through_cache<T>(slot: &Mutex<Option<T>>, fetch: impl FnOnce() -> T) -> T
where
T: Clone,
{
let mut value = slot
.lock()
.expect("Mutex should never be poisoned because service runs in a single thread");
if value.is_none() {
*value = Some(fetch());
}
value.clone().expect("Value should be populated above")
}
}