use std::collections::VecDeque;
use freenet_stdlib::prelude::{
DelegateInterfaceResult, DelegateKey, DelegateMessage, GetContractRequest, InboundDelegateMsg,
MessageOrigin, OutboundDelegateMsg, Parameters, PutContractRequest, SubscribeContractRequest,
UpdateContractRequest,
};
use crate::wasm_runtime::delegate_api::DelegateApiVersion;
use super::super::engine::{InstanceHandle, WasmEngine};
use super::super::native_api::{
CURRENT_DELEGATE_INSTANCE, DELEGATE_ENV, DelegateCallEnv, InstanceId,
};
use super::super::secrets_store::UserSecretContext;
use super::super::{Runtime, RuntimeResult};
use super::error::DelegateExecError;
pub(super) struct DelegateEnvGuard {
instance_id: InstanceId,
}
impl DelegateEnvGuard {
pub(super) fn new(instance_id: InstanceId) -> Self {
Self { instance_id }
}
}
impl Drop for DelegateEnvGuard {
fn drop(&mut self) {
CURRENT_DELEGATE_INSTANCE.with(|c| c.set(-1));
DELEGATE_ENV.remove(&self.instance_id);
}
}
impl Runtime {
#[allow(clippy::too_many_arguments)]
pub(super) fn exec_inbound_with_env(
&mut self,
delegate_key: &DelegateKey,
params: &Parameters<'_>,
origin: Option<&MessageOrigin>,
user_context: Option<&UserSecretContext>,
msg: &InboundDelegateMsg,
context: Vec<u8>,
handle: &InstanceHandle,
instance_id: i64,
api_version: DelegateApiVersion,
) -> RuntimeResult<(Vec<OutboundDelegateMsg>, Vec<u8>)> {
let origin_contracts = match origin {
Some(MessageOrigin::WebApp(contract_id)) => vec![*contract_id],
Some(MessageOrigin::Delegate(_)) | None => Vec::new(),
Some(other) => {
tracing::warn!(
delegate_key = %delegate_key,
origin = ?other,
"Unknown MessageOrigin variant reached fail-closed default; \
wasm_runtime::delegate::Runtime::inbound_app_message must \
decide explicitly whether this variant grants contract access"
);
Vec::new()
}
};
let env = unsafe {
DelegateCallEnv::new(
context,
&mut self.secret_store,
&self.contract_store,
self.state_store_db.clone(),
self.state_write_callback.clone(),
delegate_key.clone(),
&mut self.delegate_store,
0, origin_contracts,
user_context.cloned(),
)
};
debug_assert!(
!DELEGATE_ENV.contains_key(&instance_id),
"Instance ID {instance_id} already exists in DELEGATE_ENV - this indicates a bug"
);
DELEGATE_ENV.insert(instance_id, env);
CURRENT_DELEGATE_INSTANCE.with(|c| c.set(instance_id));
let _guard = DelegateEnvGuard::new(instance_id);
let result = self.exec_inbound(params, origin, msg, handle, api_version);
let updated_context = DELEGATE_ENV
.get(&instance_id)
.map(|env| env.context.clone())
.unwrap_or_default();
let outbound = result?;
Ok((outbound, updated_context))
}
pub(super) fn exec_inbound(
&mut self,
params: &Parameters<'_>,
origin: Option<&MessageOrigin>,
msg: &InboundDelegateMsg,
handle: &InstanceHandle,
api_version: DelegateApiVersion,
) -> RuntimeResult<Vec<OutboundDelegateMsg>> {
let param_buf_ptr = {
let mut param_buf = self.init_buf(handle, params)?;
param_buf.write(params)?;
param_buf.ptr()
};
let origin_buf_ptr = {
let bytes = match origin {
Some(o) => bincode::serialize(o)?,
None => Vec::new(),
};
let mut origin_buf = self.init_buf(handle, &bytes)?;
origin_buf.write(bytes)?;
origin_buf.ptr()
};
let msg_ptr = {
let msg = bincode::serialize(msg)?;
let mut msg_buf = self.init_buf(handle, &msg)?;
msg_buf.write(msg)?;
msg_buf.ptr()
};
let inbound_msg_name = match msg {
InboundDelegateMsg::ApplicationMessage(_) => "ApplicationMessage",
InboundDelegateMsg::UserResponse(_) => "UserResponse",
InboundDelegateMsg::GetContractResponse(_) => "GetContractResponse",
InboundDelegateMsg::PutContractResponse(_) => "PutContractResponse",
InboundDelegateMsg::UpdateContractResponse(_) => "UpdateContractResponse",
InboundDelegateMsg::SubscribeContractResponse(_) => "SubscribeContractResponse",
InboundDelegateMsg::ContractNotification(_) => "ContractNotification",
InboundDelegateMsg::DelegateMessage(_) => "DelegateMessage",
_ => "Unknown",
};
tracing::debug!(
inbound_msg_name,
api_version = %api_version,
"Calling delegate with inbound message"
);
let res = match api_version {
DelegateApiVersion::V1 => {
self.engine.call_3i64(
handle,
"process",
param_buf_ptr as i64,
origin_buf_ptr as i64,
msg_ptr as i64,
)?
}
DelegateApiVersion::V2 => {
self.engine.call_3i64_async_imports(
handle,
"process",
param_buf_ptr as i64,
origin_buf_ptr as i64,
msg_ptr as i64,
)?
}
};
let linear_mem = self.linear_mem(handle)?;
let outbound = unsafe {
DelegateInterfaceResult::from_raw(res, &linear_mem)
.unwrap(linear_mem)
.map_err(Into::<DelegateExecError>::into)?
};
self.log_delegate_exec_result(inbound_msg_name, &outbound);
Ok(outbound)
}
pub(super) fn log_delegate_exec_result(
&self,
inbound_msg_name: &str,
outbound: &[OutboundDelegateMsg],
) {
if tracing::enabled!(tracing::Level::DEBUG) {
let outbound_message_names = outbound
.iter()
.map(|m| match m {
OutboundDelegateMsg::ApplicationMessage(am) => format!(
"ApplicationMessage(payload_len={}, processed={}, context_len={})",
am.payload.len(),
am.processed,
am.context.as_ref().len()
),
OutboundDelegateMsg::RequestUserInput(_) => "RequestUserInput".to_string(),
OutboundDelegateMsg::ContextUpdated(_) => "ContextUpdated".to_string(),
OutboundDelegateMsg::GetContractRequest(req) => {
format!("GetContractRequest(contract={})", req.contract_id)
}
OutboundDelegateMsg::PutContractRequest(req) => {
format!("PutContractRequest(contract={})", req.contract.key())
}
OutboundDelegateMsg::UpdateContractRequest(req) => {
format!("UpdateContractRequest(contract={})", req.contract_id)
}
OutboundDelegateMsg::SubscribeContractRequest(req) => {
format!("SubscribeContractRequest(contract={})", req.contract_id)
}
OutboundDelegateMsg::SendDelegateMessage(msg) => {
format!(
"SendDelegateMessage(target={}, payload_len={})",
msg.target,
msg.payload.len()
)
}
})
.collect::<Vec<String>>()
.join(", ");
tracing::debug!(
inbound_msg_name,
outbound_message_names,
"Delegate returned outbound messages"
);
} else {
tracing::debug!(
inbound_msg_name,
outbound_len = outbound.len(),
"Delegate returned outbound messages"
);
}
}
pub(super) fn log_process_outbound_entry(
&self,
delegate_key: &DelegateKey,
origin: Option<&MessageOrigin>,
outbound_msgs: &VecDeque<OutboundDelegateMsg>,
) {
tracing::debug!(
delegate_key = ?delegate_key,
?origin,
outbound_msgs_len = outbound_msgs.len(),
outbound_msg_details = debug(if tracing::enabled!(tracing::Level::DEBUG) {
outbound_msgs.iter().map(|msg| {
match msg {
OutboundDelegateMsg::ApplicationMessage(m) => format!("AppMsg(payload_len={})", m.payload.len()),
OutboundDelegateMsg::RequestUserInput(_) => "UserInputReq".to_string(),
OutboundDelegateMsg::ContextUpdated(_) => "ContextUpdate".to_string(),
OutboundDelegateMsg::GetContractRequest(r) => format!("GetContractReq({})", r.contract_id),
OutboundDelegateMsg::PutContractRequest(r) => format!("PutContractReq({})", r.contract.key()),
OutboundDelegateMsg::UpdateContractRequest(r) => format!("UpdateContractReq({})", r.contract_id),
OutboundDelegateMsg::SubscribeContractRequest(r) => format!("SubscribeContractReq({})", r.contract_id),
OutboundDelegateMsg::SendDelegateMessage(m) => format!("SendDelegateMsg(target={})", m.target),
}
}).collect::<Vec<_>>()
} else {
Vec::new()
}),
"process_outbound called"
);
}
#[allow(clippy::too_many_arguments)]
pub(super) fn process_outbound(
&mut self,
delegate_key: &DelegateKey,
_handle: &InstanceHandle,
_instance_id: i64,
_params: &Parameters<'_>,
origin: Option<&MessageOrigin>,
outbound_msgs: &mut VecDeque<OutboundDelegateMsg>,
context: &mut Vec<u8>,
results: &mut Vec<OutboundDelegateMsg>,
) -> RuntimeResult<()> {
self.log_process_outbound_entry(delegate_key, origin, outbound_msgs);
while let Some(outbound) = outbound_msgs.pop_front() {
match outbound {
OutboundDelegateMsg::ApplicationMessage(mut msg) => {
tracing::debug!(
payload_len = msg.payload.len(),
processed = msg.processed,
"Adding ApplicationMessage to results"
);
msg.context = freenet_stdlib::prelude::DelegateContext::default();
results.push(OutboundDelegateMsg::ApplicationMessage(msg));
for remaining in outbound_msgs.drain(..) {
results.push(remaining);
}
break;
}
OutboundDelegateMsg::RequestUserInput(req) => {
tracing::debug!(
request_id = req.request_id,
"Passing RequestUserInput to executor for user prompting"
);
results.push(OutboundDelegateMsg::RequestUserInput(req));
for remaining in outbound_msgs.drain(..) {
results.push(remaining);
}
break;
}
OutboundDelegateMsg::ContextUpdated(new_context) => {
context.clear();
context.extend_from_slice(new_context.as_ref());
}
OutboundDelegateMsg::GetContractRequest(req) if !req.processed => {
tracing::debug!(
contract_id = %req.contract_id,
"Passing GetContractRequest to executor for async handling"
);
results.push(OutboundDelegateMsg::GetContractRequest(req));
for remaining in outbound_msgs.drain(..) {
results.push(remaining);
}
break;
}
OutboundDelegateMsg::GetContractRequest(GetContractRequest {
context: ctx,
..
}) => {
tracing::debug!("GetContractRequest processed");
context.clear();
context.extend_from_slice(ctx.as_ref());
}
OutboundDelegateMsg::PutContractRequest(req) if !req.processed => {
tracing::debug!(
contract = %req.contract.key(),
"Passing PutContractRequest to executor for async handling"
);
results.push(OutboundDelegateMsg::PutContractRequest(req));
for remaining in outbound_msgs.drain(..) {
results.push(remaining);
}
break;
}
OutboundDelegateMsg::PutContractRequest(PutContractRequest {
context: ctx,
..
}) => {
tracing::debug!("PutContractRequest processed");
context.clear();
context.extend_from_slice(ctx.as_ref());
}
OutboundDelegateMsg::UpdateContractRequest(req) if !req.processed => {
tracing::debug!(
contract_id = %req.contract_id,
"Passing UpdateContractRequest to executor for async handling"
);
results.push(OutboundDelegateMsg::UpdateContractRequest(req));
for remaining in outbound_msgs.drain(..) {
results.push(remaining);
}
break;
}
OutboundDelegateMsg::UpdateContractRequest(UpdateContractRequest {
context: ctx,
..
}) => {
tracing::debug!("UpdateContractRequest processed");
context.clear();
context.extend_from_slice(ctx.as_ref());
}
OutboundDelegateMsg::SubscribeContractRequest(req) if !req.processed => {
tracing::debug!(
contract_id = %req.contract_id,
"Passing SubscribeContractRequest to executor for async handling"
);
results.push(OutboundDelegateMsg::SubscribeContractRequest(req));
for remaining in outbound_msgs.drain(..) {
results.push(remaining);
}
break;
}
OutboundDelegateMsg::SubscribeContractRequest(SubscribeContractRequest {
context: ctx,
..
}) => {
tracing::debug!("SubscribeContractRequest processed");
context.clear();
context.extend_from_slice(ctx.as_ref());
}
OutboundDelegateMsg::SendDelegateMessage(mut msg) if !msg.processed => {
tracing::debug!(
target_delegate = %msg.target,
"Passing SendDelegateMessage to executor for delivery"
);
msg.sender = delegate_key.clone();
results.push(OutboundDelegateMsg::SendDelegateMessage(msg));
for remaining in outbound_msgs.drain(..) {
match remaining {
OutboundDelegateMsg::SendDelegateMessage(mut m) if !m.processed => {
m.sender = delegate_key.clone();
results.push(OutboundDelegateMsg::SendDelegateMessage(m));
}
msg @ (OutboundDelegateMsg::ApplicationMessage(_)
| OutboundDelegateMsg::RequestUserInput(_)
| OutboundDelegateMsg::ContextUpdated(_)
| OutboundDelegateMsg::GetContractRequest(_)
| OutboundDelegateMsg::PutContractRequest(_)
| OutboundDelegateMsg::UpdateContractRequest(_)
| OutboundDelegateMsg::SubscribeContractRequest(_)
| OutboundDelegateMsg::SendDelegateMessage(_)) => results.push(msg),
}
}
break;
}
OutboundDelegateMsg::SendDelegateMessage(DelegateMessage {
context: ctx, ..
}) => {
tracing::debug!("SendDelegateMessage processed");
context.clear();
context.extend_from_slice(ctx.as_ref());
}
}
}
Ok(())
}
}