use std::sync::Arc;
use freenet_stdlib::prelude::{
ContractCode, ContractContainer, ContractInstanceId, ContractKey, ContractWasmAPIVersion,
Parameters, RelatedContracts, StateSummary, UpdateData, UpdateModification, ValidateResult,
WrappedContract, WrappedState,
};
use super::oracle::{ConformanceOracle, OracleError};
use crate::wasm_runtime::{
ContractError as RuntimeContractError, ContractExecError, ContractRuntimeInterface,
ContractStore, DelegateStore, Runtime, RuntimeInnerError, SecretsStore,
};
const STANDALONE_CONTRACT_STORE_BYTES: u64 = 1024 * 1024 * 1024;
const STANDALONE_DELEGATE_STORE_BYTES: u64 = 10_000_000;
#[derive(Debug, thiserror::Error)]
pub enum OracleBuildError {
#[error("could not create scratch directory for the conformance runtime: {0}")]
Scratch(#[from] std::io::Error),
#[error("could not open the scratch storage backend: {0}")]
Storage(String),
#[error("could not build the contract runtime: {0}")]
Runtime(#[from] RuntimeContractError),
}
pub struct RuntimeOracle {
runtime: Runtime,
key: ContractKey,
parameters: Parameters<'static>,
_scratch: Option<tempfile::TempDir>,
}
impl RuntimeOracle {
pub async fn standalone(wasm: Vec<u8>, parameters: Vec<u8>) -> Result<Self, OracleBuildError> {
let scratch = tempfile::TempDir::new()?;
let db = crate::contract::storages::Storage::new(scratch.path())
.await
.map_err(|e| OracleBuildError::Storage(e.to_string()))?;
let contract_store = ContractStore::new(
scratch.path().join("contract"),
STANDALONE_CONTRACT_STORE_BYTES,
db.clone(),
)?;
let delegate_store = DelegateStore::new(
scratch.path().join("delegate"),
STANDALONE_DELEGATE_STORE_BYTES,
db.clone(),
)?;
let cache_dir = scratch.path().join("wasm-cache");
std::fs::create_dir_all(&cache_dir)?;
let secrets_dir = scratch.path().join("secrets");
std::fs::create_dir_all(&secrets_dir)?;
let secrets = crate::config::Secrets::load_for_secrets_dir(&secrets_dir)?;
let secrets_store = SecretsStore::new(secrets_dir, secrets, db)?;
let config = crate::wasm_runtime::RuntimeConfig {
wasmtime_cache_dir: Some(cache_dir),
..Default::default()
};
let mut runtime = Runtime::build_with_config(
contract_store,
delegate_store,
secrets_store,
false,
config,
)?;
let parameters = Parameters::from(parameters);
let container = ContractContainer::Wasm(ContractWasmAPIVersion::V1(WrappedContract::new(
Arc::new(ContractCode::from(wasm)),
parameters.clone(),
)));
let key = container.key();
runtime.contract_store.store_contract(container)?;
let parameters_for_check = parameters.clone();
let key_for_check = key;
let (mut runtime, compiled) = tokio::task::spawn_blocking(move || {
let outcome = runtime.compile_check(&key_for_check, ¶meters_for_check);
(runtime, outcome)
})
.await
.map_err(|e| OracleBuildError::Storage(format!("compile task failed: {e}")))?;
compiled?;
let _ = &mut runtime;
Ok(Self {
runtime,
key,
parameters,
_scratch: Some(scratch),
})
}
pub fn key(&self) -> &ContractKey {
&self.key
}
pub fn instance_id(&self) -> ContractInstanceId {
*self.key.id()
}
pub fn parameters(&self) -> &Parameters<'static> {
&self.parameters
}
}
impl ConformanceOracle for RuntimeOracle {
fn validate_state(
&mut self,
state: &[u8],
related: &RelatedContracts<'_>,
) -> Result<ValidateResult, OracleError> {
let state = WrappedState::new(state.to_vec());
self.runtime
.validate_state(&self.key, &self.parameters, &state, related)
.map_err(classify)
}
fn update_state(
&mut self,
state: &[u8],
updates: &[UpdateData<'_>],
) -> Result<UpdateModification<'static>, OracleError> {
let state = WrappedState::new(state.to_vec());
self.runtime
.update_state(&self.key, &self.parameters, &state, updates)
.map_err(classify)
}
fn summarize_state(&mut self, state: &[u8]) -> Result<Vec<u8>, OracleError> {
let state = WrappedState::new(state.to_vec());
self.runtime
.summarize_state(&self.key, &self.parameters, &state)
.map(StateSummary::into_bytes)
.map_err(classify)
}
fn get_state_delta(&mut self, state: &[u8], summary: &[u8]) -> Result<Vec<u8>, OracleError> {
let state = WrappedState::new(state.to_vec());
let summary = StateSummary::from(summary.to_vec());
self.runtime
.get_state_delta(&self.key, &self.parameters, &state, &summary)
.map(|delta| delta.into_bytes())
.map_err(classify)
}
}
fn classify(err: RuntimeContractError) -> OracleError {
match err.deref() {
RuntimeInnerError::ContractExecError(exec) => match exec {
ContractExecError::OutOfGas
| ContractExecError::MaxComputeTimeExceeded
| ContractExecError::SchedulerOverloaded => OracleError::resource(err.to_string()),
ContractExecError::ContractError(_) => OracleError::contract(err.to_string()),
ContractExecError::DoublePut(_)
| ContractExecError::InvalidArrayLength(_)
| ContractExecError::MissingContractExports { .. }
| ContractExecError::UnexpectedResult => OracleError::runtime(err.to_string()),
},
RuntimeInnerError::Any(_)
| RuntimeInnerError::BufferError(_)
| RuntimeInnerError::IOError(_)
| RuntimeInnerError::SecretStoreError(_)
| RuntimeInnerError::Serialization(_)
| RuntimeInnerError::DelegateNotFound(_)
| RuntimeInnerError::DelegateIdentityMismatch { .. }
| RuntimeInnerError::DelegateExecError(_)
| RuntimeInnerError::ContractNotFound(_)
| RuntimeInnerError::ContractIdentityMismatch { .. }
| RuntimeInnerError::WasmError(_) => OracleError::runtime(err.to_string()),
}
}