use super::*;
impl Executor<Runtime> {
pub fn export_user_secrets(
&self,
user_context: &UserSecretContext,
token: &[u8],
) -> Result<Vec<u8>, ExecutorError> {
use crate::wasm_runtime::secret_export::BundleKeyMaterial;
self.runtime
.export_secret_bundle(user_context.scope(), &BundleKeyMaterial::Token(token))
.map_err(|e| ExecutorError::other(anyhow::anyhow!("secret export failed: {e}")))
}
pub fn delegate_request(
&mut self,
req: DelegateRequest<'_>,
origin_contract: Option<&ContractInstanceId>,
caller_delegate: Option<&DelegateKey>,
user_context: Option<&UserSecretContext>,
) -> Response {
debug_assert!(
!(origin_contract.is_some() && caller_delegate.is_some()),
"execute_delegate_request: at most one of origin_contract and \
caller_delegate may be Some (got both)"
);
tracing::debug!(
origin_contract = ?origin_contract,
caller_delegate = ?caller_delegate.map(|k| k.to_string()),
"received delegate request"
);
match req {
DelegateRequest::RegisterDelegate {
delegate,
cipher,
nonce,
} => {
use chacha20poly1305::{KeyInit, XChaCha20Poly1305};
let key = delegate.key().clone();
let arr = (&cipher).into();
let cipher = XChaCha20Poly1305::new(arr);
let nonce = nonce.into();
if let Some(contract) = origin_contract {
self.delegate_origin_ids
.entry(key.clone())
.or_default()
.push(*contract);
}
match self.runtime.register_delegate(delegate, cipher, nonce) {
Ok(_) => Ok(DelegateResponse {
key,
values: Vec::new(),
}),
Err(err) => {
tracing::warn!(
delegate_key = %key,
error = %err,
phase = "register_failed",
"Failed to register delegate"
);
Err(ExecutorError::other(StdDelegateError::RegisterError(key)))
}
}
}
DelegateRequest::UnregisterDelegate(key) => {
self.delegate_origin_ids.remove(&key);
crate::wasm_runtime::DELEGATE_SUBSCRIPTIONS.retain(|_, subscribers| {
subscribers.remove(&key);
!subscribers.is_empty()
});
crate::wasm_runtime::DELEGATE_INHERITED_ORIGINS.remove(&key);
{
use std::sync::atomic::Ordering;
let count = &crate::wasm_runtime::CREATED_DELEGATES_COUNT;
let prev = count.load(Ordering::Relaxed);
if prev > 0 {
count.fetch_sub(1, Ordering::Relaxed);
}
}
match self.runtime.unregister_delegate(&key) {
Ok(_) => Ok(HostResponse::Ok),
Err(err) => {
tracing::warn!(
delegate_key = %key,
error = %err,
phase = "unregister_failed",
"Failed to unregister delegate"
);
Ok(HostResponse::Ok)
}
}
}
DelegateRequest::ApplicationMessages {
key,
inbound,
params,
} => {
let origin = resolve_message_origin(caller_delegate, origin_contract, &key);
match self.runtime.inbound_app_message(
&key,
¶ms,
origin.as_ref(),
user_context,
inbound
.into_iter()
.map(InboundDelegateMsg::into_owned)
.collect(),
) {
Ok(values) => Ok(DelegateResponse { key, values }),
Err(err) => {
let key_display = key.to_string();
let exec_err =
ExecutorError::execution(err, Some(InnerOpError::Delegate(key)));
if exec_err.is_missing_delegate() {
tracing::warn!(
delegate_key = %key_display,
"Delegate not found in store (expected for migration probes)"
);
} else {
tracing::error!(
delegate_key = %key_display,
error = %exec_err,
phase = "execution_failed",
"Failed executing delegate"
);
}
Err(exec_err)
}
}
}
_ => Err(ExecutorError::other(anyhow::anyhow!("not supported"))),
}
}
}
fn resolve_message_origin(
caller_delegate: Option<&DelegateKey>,
origin_contract: Option<&ContractInstanceId>,
delegate_key: &DelegateKey,
) -> Option<MessageOrigin> {
if let Some(caller) = caller_delegate {
Some(MessageOrigin::Delegate(caller.clone()))
} else if let Some(contract_id) = origin_contract {
Some(MessageOrigin::WebApp(*contract_id))
} else {
crate::wasm_runtime::DELEGATE_INHERITED_ORIGINS
.get(delegate_key)
.and_then(|entry| entry.origins.first().copied().map(MessageOrigin::WebApp))
}
}
#[cfg(test)]
mod resolve_message_origin_tests {
use super::*;
use freenet_stdlib::prelude::CodeHash;
fn dkey(seed: u8) -> DelegateKey {
DelegateKey::new([seed; 32], CodeHash::new([seed; 32]))
}
#[test]
fn caller_delegate_takes_precedence_over_origin_contract() {
let caller = dkey(0xA1);
let recipient = dkey(0xB2);
let app_contract = ContractInstanceId::new([0xC3; 32]);
let origin = resolve_message_origin(Some(&caller), Some(&app_contract), &recipient);
match origin {
Some(MessageOrigin::Delegate(k)) => assert_eq!(k, caller),
other => panic!("Expected Delegate(caller), got {other:?}"),
}
}
#[test]
fn origin_contract_alone_yields_webapp() {
let recipient = dkey(0xB2);
let app_contract = ContractInstanceId::new([0xC3; 32]);
let origin = resolve_message_origin(None, Some(&app_contract), &recipient);
match origin {
Some(MessageOrigin::WebApp(id)) => assert_eq!(id, app_contract),
other => panic!("Expected WebApp(app_contract), got {other:?}"),
}
}
#[test]
fn no_arguments_and_no_inherited_yields_none() {
let recipient = dkey(0xEE);
crate::wasm_runtime::DELEGATE_INHERITED_ORIGINS.remove(&recipient);
let origin = resolve_message_origin(None, None, &recipient);
assert!(origin.is_none(), "Expected None, got {origin:?}");
}
#[test]
fn caller_delegate_overrides_inherited_origin() {
let caller = dkey(0xA1);
let recipient = dkey(0xB3);
let inherited_contract = ContractInstanceId::new([0xDD; 32]);
crate::wasm_runtime::DELEGATE_INHERITED_ORIGINS.insert(
recipient.clone(),
crate::wasm_runtime::InheritedOriginsEntry::new(vec![inherited_contract]),
);
let origin = resolve_message_origin(Some(&caller), None, &recipient);
crate::wasm_runtime::DELEGATE_INHERITED_ORIGINS.remove(&recipient);
match origin {
Some(MessageOrigin::Delegate(k)) => assert_eq!(k, caller),
other => panic!("Expected Delegate(caller), got {other:?}"),
}
}
#[test]
fn inherited_origin_fallback_yields_webapp() {
use crate::wasm_runtime::{DELEGATE_INHERITED_ORIGINS, InheritedOriginsEntry};
let recipient = dkey(0xC5);
let contract = ContractInstanceId::new([0xC6; 32]);
DELEGATE_INHERITED_ORIGINS.insert(
recipient.clone(),
InheritedOriginsEntry::new(vec![contract]),
);
let origin = resolve_message_origin(None, None, &recipient);
DELEGATE_INHERITED_ORIGINS.remove(&recipient);
assert!(
matches!(origin, Some(MessageOrigin::WebApp(c)) if c == contract),
"fallback must yield the inherited WebApp origin, got {origin:?}"
);
}
}