use std::sync::Arc;
use parking_lot::Mutex;
use stoffel_vm_types::core_types::{ForeignObjectRef, Value};
use stoffel_vm_types::functions::VMFunction;
use tokio::task::JoinHandle;
use crate::core_vm::{VirtualMachine, VmEntryInvocation};
use crate::foreign_functions::{ForeignFunctionCallbackResult, ForeignFunctionContext};
use crate::net::client_store::ClientInputHydrationCount;
use crate::net::hb_engine::HoneyBadgerMpcEngine;
use crate::net::mpc_engine::{
AsyncMpcEngine, MpcEngine, MpcInstanceId, MpcPartyCount, MpcPartyId, MpcSessionTopology,
MpcThreshold,
};
use crate::storage::preproc::{LmdbPreprocStore, PreprocStore};
use crate::VirtualMachineResult;
use super::config::{MpcExecutionResult, MpcRunnerConfig};
use super::error::{MpcRunnerBackendResultExt, MpcRunnerError, MpcRunnerResult};
use super::guard::RunnerVmGuard;
pub struct MpcRunner<E = HoneyBadgerMpcEngine<ark_bls12_381::Fr, ark_bls12_381::G1Projective>> {
vm: Arc<Mutex<Option<VirtualMachine>>>,
mpc_engine: Arc<E>,
message_processor: Option<JoinHandle<()>>,
config: MpcRunnerConfig,
}
impl<E> MpcRunner<E>
where
E: MpcEngine + 'static,
{
pub fn new(vm: VirtualMachine, mpc_engine: Arc<E>) -> Self {
Self::with_config(vm, mpc_engine, MpcRunnerConfig::default())
}
pub fn with_config(
mut vm: VirtualMachine,
mpc_engine: Arc<E>,
config: MpcRunnerConfig,
) -> Self {
vm.set_mpc_engine(mpc_engine.clone());
Self {
vm: Arc::new(Mutex::new(Some(vm))),
mpc_engine,
message_processor: None,
config,
}
}
pub fn attach_message_processor(&mut self, handle: JoinHandle<()>) {
self.message_processor = Some(handle);
}
pub fn hydrate_client_inputs(&self) -> MpcRunnerResult<ClientInputHydrationCount> {
self.try_with_vm_result(|vm| vm.hydrate_from_mpc_engine())
}
pub fn try_with_vm<R, F>(&self, inspect: F) -> MpcRunnerResult<R>
where
F: FnOnce(&VirtualMachine) -> R,
{
let vm = self.vm.lock();
let vm = vm.as_ref().ok_or(MpcRunnerError::VmAlreadyExecuting)?;
Ok(inspect(vm))
}
pub fn try_with_vm_result<R, F>(&self, inspect: F) -> MpcRunnerResult<R>
where
F: FnOnce(&VirtualMachine) -> VirtualMachineResult<R>,
{
let vm = self.vm.lock();
let vm = vm.as_ref().ok_or(MpcRunnerError::VmAlreadyExecuting)?;
inspect(vm).map_err(MpcRunnerError::from)
}
pub fn try_with_vm_mut<R, F>(&self, mutate: F) -> MpcRunnerResult<R>
where
F: FnOnce(&mut VirtualMachine) -> R,
{
let mut vm = self.vm.lock();
let vm = vm.as_mut().ok_or(MpcRunnerError::VmAlreadyExecuting)?;
Ok(mutate(vm))
}
pub fn try_with_vm_mut_result<R, F>(&self, mutate: F) -> MpcRunnerResult<R>
where
F: FnOnce(&mut VirtualMachine) -> VirtualMachineResult<R>,
{
let mut vm = self.vm.lock();
let vm = vm.as_mut().ok_or(MpcRunnerError::VmAlreadyExecuting)?;
mutate(vm).map_err(MpcRunnerError::from)
}
pub async fn execute_function_blocking(
&self,
function_name: &str,
) -> MpcRunnerResult<MpcExecutionResult<Value>> {
let clients_hydrated = if self.config.auto_hydrate {
self.hydrate_client_inputs()?
} else {
ClientInputHydrationCount::zero()
};
let vm_arc = self.vm.clone();
let fn_name = function_name.to_string();
let timeout = self.config.execution_timeout;
let result = tokio::time::timeout(timeout, async {
tokio::task::spawn_blocking(move || {
let mut vm_guard = RunnerVmGuard::take(&vm_arc)?;
let execution = vm_guard
.vm_mut()?
.execute(&fn_name)
.map_err(MpcRunnerError::from);
vm_guard.restore()?;
execution
})
.await
.map_err(|source| MpcRunnerError::Join { source })?
})
.await
.map_err(|_| MpcRunnerError::ExecutionTimedOut { timeout })??;
Ok(MpcExecutionResult {
value: result,
clients_hydrated,
})
}
#[deprecated(
since = "0.1.0",
note = "prefer try_with_vm, try_with_vm_result, try_with_vm_mut, try_with_vm_mut_result, or typed registration helpers"
)]
pub fn vm(&self) -> &Arc<Mutex<Option<VirtualMachine>>> {
&self.vm
}
pub fn mpc_engine(&self) -> &Arc<E> {
&self.mpc_engine
}
pub fn try_register_function(&self, function: VMFunction) -> MpcRunnerResult<()> {
self.try_with_vm_mut_result(|vm| vm.try_register_function(function))
}
pub fn try_register_foreign_function<F>(&self, name: &str, func: F) -> MpcRunnerResult<()>
where
F: Fn(ForeignFunctionContext) -> Result<Value, String> + 'static + Send + Sync,
{
self.try_with_vm_mut_result(|vm| vm.try_register_foreign_function(name, func))
}
pub fn try_register_typed_foreign_function<F>(&self, name: &str, func: F) -> MpcRunnerResult<()>
where
F: Fn(ForeignFunctionContext) -> ForeignFunctionCallbackResult<Value>
+ 'static
+ Send
+ Sync,
{
self.try_with_vm_mut_result(|vm| vm.try_register_typed_foreign_function(name, func))
}
pub fn try_register_foreign_object<T: 'static + Send + Sync>(
&self,
object: T,
) -> MpcRunnerResult<ForeignObjectRef> {
self.try_with_vm_mut_result(|vm| vm.try_register_foreign_object(object))
}
pub fn try_register_foreign_object_ref<T: 'static + Send + Sync>(
&self,
object: T,
) -> MpcRunnerResult<ForeignObjectRef> {
self.try_register_foreign_object(object)
}
pub fn try_register_foreign_object_value<T: 'static + Send + Sync>(
&self,
object: T,
) -> MpcRunnerResult<Value> {
self.try_register_foreign_object(object).map(Value::from)
}
#[track_caller]
pub fn register_function(&self, function: VMFunction) {
self.try_register_function(function)
.expect("invalid VM function registration");
}
pub async fn shutdown(mut self) {
if let Some(handle) = self.message_processor.take() {
handle.abort();
let _ = handle.await;
}
self.mpc_engine.shutdown();
}
pub fn is_ready(&self) -> bool {
self.mpc_engine.is_ready()
}
pub fn topology(&self) -> MpcSessionTopology {
self.mpc_engine.topology()
}
pub fn instance(&self) -> MpcInstanceId {
self.topology().instance()
}
pub fn party(&self) -> MpcPartyId {
self.topology().party()
}
pub fn party_count(&self) -> MpcPartyCount {
self.topology().party_count()
}
pub fn threshold_param(&self) -> MpcThreshold {
self.topology().threshold_param()
}
pub fn set_preproc_store(
&self,
store: Arc<dyn PreprocStore>,
program_hash: [u8; 32],
) -> MpcRunnerResult<()> {
self.mpc_engine
.preproc_persistence_ops()
.map_mpc_runner_backend_err("preproc_persistence_ops")?
.set_preproc_store(store, program_hash)
.map_mpc_runner_backend_err("set_preproc_store")
}
pub fn enable_preproc_persistence(&self, program_hash: [u8; 32]) -> MpcRunnerResult<()> {
let store = Arc::new(LmdbPreprocStore::open(LmdbPreprocStore::default_path())?);
self.set_preproc_store(store, program_hash)
}
}
impl<E> MpcRunner<E>
where
E: MpcEngine + AsyncMpcEngine + 'static,
{
async fn hydrate_client_inputs_for_async_execution(
&self,
) -> MpcRunnerResult<ClientInputHydrationCount> {
let store = self.try_with_vm_result(|vm| {
vm.client_input_store_for_async_engine(self.mpc_engine.as_ref())
})?;
let client_ops = self
.mpc_engine
.async_client_ops()
.map_mpc_runner_backend_err("async_client_ops")?;
client_ops
.hydrate_client_inputs_async(&store)
.await
.map_mpc_runner_backend_err("hydrate_client_inputs_async")
}
pub async fn execute_function(
&self,
function_name: &str,
) -> MpcRunnerResult<MpcExecutionResult<Value>> {
let clients_hydrated = if self.config.auto_hydrate {
self.hydrate_client_inputs_for_async_execution().await?
} else {
ClientInputHydrationCount::zero()
};
let timeout = self.config.execution_timeout;
let mut vm_guard = RunnerVmGuard::take(&self.vm)?;
let execution = tokio::time::timeout(
timeout,
vm_guard
.vm_mut()?
.execute_async(function_name, self.mpc_engine.as_ref()),
)
.await;
vm_guard.restore()?;
let result = execution.map_err(|_| MpcRunnerError::ExecutionTimedOut { timeout })??;
Ok(MpcExecutionResult {
value: result,
clients_hydrated,
})
}
pub async fn execute_functions<I>(
&self,
invocations: I,
) -> MpcRunnerResult<MpcExecutionResult<Vec<Value>>>
where
I: IntoIterator<Item = VmEntryInvocation>,
{
let clients_hydrated = if self.config.auto_hydrate {
self.hydrate_client_inputs_for_async_execution().await?
} else {
ClientInputHydrationCount::zero()
};
let timeout = self.config.execution_timeout;
let vm_template = self.try_with_vm_result(|vm| vm.try_clone_with_independent_state())?;
let execution = tokio::time::timeout(
timeout,
vm_template.execute_many_async_with_args(invocations, self.mpc_engine.as_ref()),
)
.await;
let result = execution.map_err(|_| MpcRunnerError::ExecutionTimedOut { timeout })??;
Ok(MpcExecutionResult {
value: result,
clients_hydrated,
})
}
}