Skip to main content

freenet_stdlib/
delegate_host.rs

1//! Host function API for delegates.
2//!
3//! This module provides synchronous access to delegate context, secrets, and
4//! contract state via host functions, eliminating the need for message round-trips.
5//!
6//! # Example
7//!
8//! ```ignore
9//! use freenet_stdlib::prelude::*;
10//!
11//! #[delegate]
12//! impl DelegateInterface for MyDelegate {
13//!     fn process(
14//!         ctx: &mut DelegateCtx,
15//!         _params: Parameters<'static>,
16//!         _attested: Option<&'static [u8]>,
17//!         message: InboundDelegateMsg,
18//!     ) -> Result<Vec<OutboundDelegateMsg>, DelegateError> {
19//!         // Read/write temporary context
20//!         let data = ctx.read();
21//!         ctx.write(b"new state");
22//!
23//!         // Access persistent secrets
24//!         if let Some(key) = ctx.get_secret(b"private_key") {
25//!             // use key...
26//!         }
27//!         ctx.set_secret(b"new_secret", b"value");
28//!
29//!         // V2: Direct contract access (no round-trips!)
30//!         let contract_id = [0u8; 32]; // your contract instance ID
31//!         if let Some(state) = ctx.get_contract_state(&contract_id) {
32//!             // process state...
33//!         }
34//!         ctx.put_contract_state(&contract_id, b"new state");
35//!
36//!         Ok(vec![])
37//!     }
38//! }
39//! ```
40//!
41//! # Context vs Secrets vs Contracts
42//!
43//! - **Context** (`read`/`write`): Temporary state within a single message batch.
44//!   Reset between separate runtime calls. Use for intermediate processing state.
45//!
46//! - **Secrets** (`get_secret`/`set_secret`): Persistent encrypted storage.
47//!   Survives across all delegate invocations. Use for private keys, tokens, etc.
48//!
49//! - **Contracts** (`get_contract_state`/`put_contract_state`/`update_contract_state`/
50//!   `subscribe_contract`): V2 host functions for direct contract state access.
51//!   Synchronous local reads/writes — no request/response round-trips.
52//!
53//! # Error Codes
54//!
55//! Host functions return negative values to indicate errors:
56//!
57//! | Code | Meaning |
58//! |------|---------|
59//! | 0    | Success |
60//! | -1   | Called outside process() context |
61//! | -2   | Secret not found |
62//! | -3   | Storage operation failed |
63//! | -4   | Invalid parameter (e.g., negative length) |
64//! | -5   | Context too large (exceeds i32::MAX) |
65//! | -6   | Buffer too small |
66//! | -7   | Contract not found in local store |
67//! | -8   | Internal state store error |
68//! | -9   | WASM memory bounds violation |
69//! | -10  | Contract code not registered |
70//!
71//! The wrapper methods in [`DelegateCtx`] handle these error codes and present
72//! a more ergonomic API.
73
74/// Error codes returned by host functions.
75///
76/// Negative values indicate errors, non-negative values indicate success
77/// (usually the number of bytes read/written).
78pub mod error_codes {
79    /// Operation succeeded.
80    pub const SUCCESS: i32 = 0;
81    /// Called outside of a process() context.
82    pub const ERR_NOT_IN_PROCESS: i32 = -1;
83    /// Secret not found.
84    pub const ERR_SECRET_NOT_FOUND: i32 = -2;
85    /// Storage operation failed.
86    pub const ERR_STORAGE_FAILED: i32 = -3;
87    /// Invalid parameter (e.g., negative length).
88    pub const ERR_INVALID_PARAM: i32 = -4;
89    /// Context too large (exceeds i32::MAX).
90    pub const ERR_CONTEXT_TOO_LARGE: i32 = -5;
91    /// Buffer too small to hold the data.
92    pub const ERR_BUFFER_TOO_SMALL: i32 = -6;
93    /// Contract not found in local store.
94    pub const ERR_CONTRACT_NOT_FOUND: i32 = -7;
95    /// Internal state store error.
96    pub const ERR_STORE_ERROR: i32 = -8;
97    /// WASM memory bounds violation (pointer/length out of range).
98    pub const ERR_MEMORY_BOUNDS: i32 = -9;
99    /// Contract code not registered in the index.
100    pub const ERR_CONTRACT_CODE_NOT_REGISTERED: i32 = -10;
101    /// Delegate creation depth limit exceeded.
102    pub const ERR_DEPTH_EXCEEDED: i32 = -20;
103    /// Per-call delegate creation limit exceeded.
104    pub const ERR_CREATIONS_EXCEEDED: i32 = -21;
105    /// Invalid WASM module (failed to construct DelegateContainer).
106    pub const ERR_INVALID_WASM: i32 = -23;
107    /// Failed to register delegate in secret/delegate store.
108    pub const ERR_STORE_FAILED: i32 = -24;
109}
110
111// ============================================================================
112// Host function declarations (WASM only)
113// ============================================================================
114
115#[cfg(target_family = "wasm")]
116#[link(wasm_import_module = "freenet_delegate_ctx")]
117extern "C" {
118    /// Returns the current context length in bytes, or negative error code.
119    fn __frnt__delegate__ctx_len() -> i32;
120    /// Reads context into the buffer at `ptr` (max `len` bytes). Returns bytes written, or negative error code.
121    fn __frnt__delegate__ctx_read(ptr: i64, len: i32) -> i32;
122    /// Writes `len` bytes from `ptr` into the context, replacing existing content. Returns 0 on success, or negative error code.
123    fn __frnt__delegate__ctx_write(ptr: i64, len: i32) -> i32;
124}
125
126#[cfg(target_family = "wasm")]
127#[link(wasm_import_module = "freenet_delegate_secrets")]
128extern "C" {
129    /// Get a secret. Returns bytes written to `out_ptr`, or negative error code.
130    fn __frnt__delegate__get_secret(key_ptr: i64, key_len: i32, out_ptr: i64, out_len: i32) -> i32;
131    /// Get secret length without fetching value. Returns length, or negative error code.
132    fn __frnt__delegate__get_secret_len(key_ptr: i64, key_len: i32) -> i32;
133    /// Store a secret. Returns 0 on success, or negative error code.
134    fn __frnt__delegate__set_secret(key_ptr: i64, key_len: i32, val_ptr: i64, val_len: i32) -> i32;
135    /// Check if a secret exists. Returns 1 if yes, 0 if no, or negative error code.
136    fn __frnt__delegate__has_secret(key_ptr: i64, key_len: i32) -> i32;
137    /// Remove a secret. Returns 0 on success, or negative error code.
138    fn __frnt__delegate__remove_secret(key_ptr: i64, key_len: i32) -> i32;
139}
140
141#[cfg(target_family = "wasm")]
142#[link(wasm_import_module = "freenet_delegate_contracts")]
143extern "C" {
144    /// Get contract state length. Returns byte count, or negative error code (i64).
145    fn __frnt__delegate__get_contract_state_len(id_ptr: i64, id_len: i32) -> i64;
146    /// Get contract state. Returns byte count written, or negative error code (i64).
147    fn __frnt__delegate__get_contract_state(
148        id_ptr: i64,
149        id_len: i32,
150        out_ptr: i64,
151        out_len: i64,
152    ) -> i64;
153    /// Put (store) contract state. Returns 0 on success, or negative error code (i64).
154    fn __frnt__delegate__put_contract_state(
155        id_ptr: i64,
156        id_len: i32,
157        state_ptr: i64,
158        state_len: i64,
159    ) -> i64;
160    /// Update contract state (requires existing state). Returns 0 on success, or negative error code (i64).
161    fn __frnt__delegate__update_contract_state(
162        id_ptr: i64,
163        id_len: i32,
164        state_ptr: i64,
165        state_len: i64,
166    ) -> i64;
167    /// Subscribe to contract updates. Returns 0 on success, or negative error code (i64).
168    fn __frnt__delegate__subscribe_contract(id_ptr: i64, id_len: i32) -> i64;
169}
170
171#[cfg(target_family = "wasm")]
172#[link(wasm_import_module = "freenet_delegate_management")]
173extern "C" {
174    /// Create a new delegate from WASM code + parameters.
175    /// Returns 0 on success, negative error code on failure.
176    /// On success, writes 32 bytes to out_key_ptr and 32 bytes to out_hash_ptr.
177    fn __frnt__delegate__create_delegate(
178        wasm_ptr: i64,
179        wasm_len: i64,
180        params_ptr: i64,
181        params_len: i64,
182        cipher_ptr: i64,
183        nonce_ptr: i64,
184        out_key_ptr: i64,
185        out_hash_ptr: i64,
186    ) -> i32;
187}
188
189// ============================================================================
190// DelegateCtx - Unified handle to context, secrets, and contracts
191// ============================================================================
192
193/// Opaque handle to the delegate's execution environment.
194///
195/// Provides access to:
196/// - **Temporary context**: State shared within a single message batch (reset between calls)
197/// - **Persistent secrets**: Encrypted storage that survives across all invocations
198/// - **Contract state** (V2): Direct synchronous access to local contract state
199///
200/// # Context Methods
201/// - [`read`](Self::read), [`write`](Self::write), [`len`](Self::len), [`clear`](Self::clear)
202///
203/// # Secret Methods
204/// - [`get_secret`](Self::get_secret), [`set_secret`](Self::set_secret),
205///   [`has_secret`](Self::has_secret), [`remove_secret`](Self::remove_secret)
206///
207/// # Contract Methods (V2)
208/// - [`get_contract_state`](Self::get_contract_state),
209///   [`put_contract_state`](Self::put_contract_state),
210///   [`update_contract_state`](Self::update_contract_state),
211///   [`subscribe_contract`](Self::subscribe_contract)
212///
213/// # Delegate Management Methods (V2)
214/// - [`create_delegate`](Self::create_delegate)
215#[derive(Default)]
216#[repr(transparent)]
217pub struct DelegateCtx {
218    _private: (),
219}
220
221impl DelegateCtx {
222    /// Creates the context handle.
223    ///
224    /// # Safety
225    ///
226    /// This should only be called by macro-generated code when the runtime
227    /// has set up the delegate execution environment.
228    #[doc(hidden)]
229    pub unsafe fn __new() -> Self {
230        Self { _private: () }
231    }
232
233    // ========================================================================
234    // Context methods (temporary state within a batch)
235    // ========================================================================
236
237    /// Returns the current context length in bytes.
238    #[inline]
239    pub fn len(&self) -> usize {
240        #[cfg(target_family = "wasm")]
241        {
242            let len = unsafe { __frnt__delegate__ctx_len() };
243            if len < 0 {
244                0
245            } else {
246                len as usize
247            }
248        }
249        #[cfg(not(target_family = "wasm"))]
250        {
251            0
252        }
253    }
254
255    /// Returns `true` if the context is empty.
256    #[inline]
257    pub fn is_empty(&self) -> bool {
258        self.len() == 0
259    }
260
261    /// Read the current context bytes.
262    ///
263    /// Returns an empty `Vec` if no context has been written.
264    pub fn read(&self) -> Vec<u8> {
265        #[cfg(target_family = "wasm")]
266        {
267            let len = unsafe { __frnt__delegate__ctx_len() };
268            if len <= 0 {
269                return Vec::new();
270            }
271            let mut buf = vec![0u8; len as usize];
272            let read = unsafe { __frnt__delegate__ctx_read(buf.as_mut_ptr() as i64, len) };
273            buf.truncate(read.max(0) as usize);
274            buf
275        }
276        #[cfg(not(target_family = "wasm"))]
277        {
278            Vec::new()
279        }
280    }
281
282    /// Read context into a provided buffer.
283    ///
284    /// Returns the number of bytes actually read.
285    pub fn read_into(&self, buf: &mut [u8]) -> usize {
286        #[cfg(target_family = "wasm")]
287        {
288            let read =
289                unsafe { __frnt__delegate__ctx_read(buf.as_mut_ptr() as i64, buf.len() as i32) };
290            read.max(0) as usize
291        }
292        #[cfg(not(target_family = "wasm"))]
293        {
294            let _ = buf;
295            0
296        }
297    }
298
299    /// Write new context bytes, replacing any existing content.
300    ///
301    /// Returns `true` on success, `false` on error.
302    pub fn write(&mut self, data: &[u8]) -> bool {
303        #[cfg(target_family = "wasm")]
304        {
305            let result =
306                unsafe { __frnt__delegate__ctx_write(data.as_ptr() as i64, data.len() as i32) };
307            result == 0
308        }
309        #[cfg(not(target_family = "wasm"))]
310        {
311            let _ = data;
312            false
313        }
314    }
315
316    /// Clear the context.
317    #[inline]
318    pub fn clear(&mut self) {
319        self.write(&[]);
320    }
321
322    // ========================================================================
323    // Secret methods (persistent encrypted storage)
324    // ========================================================================
325
326    /// Get the length of a secret without retrieving its value.
327    ///
328    /// Returns `None` if the secret does not exist.
329    pub fn get_secret_len(&self, key: &[u8]) -> Option<usize> {
330        #[cfg(target_family = "wasm")]
331        {
332            let result =
333                unsafe { __frnt__delegate__get_secret_len(key.as_ptr() as i64, key.len() as i32) };
334            if result < 0 {
335                None
336            } else {
337                Some(result as usize)
338            }
339        }
340        #[cfg(not(target_family = "wasm"))]
341        {
342            let _ = key;
343            None
344        }
345    }
346
347    /// Get a secret by key.
348    ///
349    /// Returns `None` if the secret does not exist.
350    pub fn get_secret(&self, key: &[u8]) -> Option<Vec<u8>> {
351        #[cfg(target_family = "wasm")]
352        {
353            // First get the length to allocate the right buffer size
354            let len = self.get_secret_len(key)?;
355
356            if len == 0 {
357                return Some(Vec::new());
358            }
359
360            let mut out = vec![0u8; len];
361            let result = unsafe {
362                __frnt__delegate__get_secret(
363                    key.as_ptr() as i64,
364                    key.len() as i32,
365                    out.as_mut_ptr() as i64,
366                    out.len() as i32,
367                )
368            };
369            if result < 0 {
370                None
371            } else {
372                out.truncate(result as usize);
373                Some(out)
374            }
375        }
376        #[cfg(not(target_family = "wasm"))]
377        {
378            let _ = key;
379            None
380        }
381    }
382
383    /// Store a secret.
384    ///
385    /// Returns `true` on success, `false` on error.
386    pub fn set_secret(&mut self, key: &[u8], value: &[u8]) -> bool {
387        #[cfg(target_family = "wasm")]
388        {
389            let result = unsafe {
390                __frnt__delegate__set_secret(
391                    key.as_ptr() as i64,
392                    key.len() as i32,
393                    value.as_ptr() as i64,
394                    value.len() as i32,
395                )
396            };
397            result == 0
398        }
399        #[cfg(not(target_family = "wasm"))]
400        {
401            let _ = (key, value);
402            false
403        }
404    }
405
406    /// Check if a secret exists.
407    pub fn has_secret(&self, key: &[u8]) -> bool {
408        #[cfg(target_family = "wasm")]
409        {
410            let result =
411                unsafe { __frnt__delegate__has_secret(key.as_ptr() as i64, key.len() as i32) };
412            result == 1
413        }
414        #[cfg(not(target_family = "wasm"))]
415        {
416            let _ = key;
417            false
418        }
419    }
420
421    /// Remove a secret.
422    ///
423    /// Returns `true` if the secret was removed, `false` if it didn't exist.
424    pub fn remove_secret(&mut self, key: &[u8]) -> bool {
425        #[cfg(target_family = "wasm")]
426        {
427            let result =
428                unsafe { __frnt__delegate__remove_secret(key.as_ptr() as i64, key.len() as i32) };
429            result == 0
430        }
431        #[cfg(not(target_family = "wasm"))]
432        {
433            let _ = key;
434            false
435        }
436    }
437
438    // ========================================================================
439    // Contract methods (V2 — direct synchronous access)
440    // ========================================================================
441
442    /// Get contract state by instance ID.
443    ///
444    /// Returns `Some(state_bytes)` if the contract exists locally,
445    /// `None` if not found or on error.
446    ///
447    /// Uses a two-step protocol: first queries the state length, then reads
448    /// the state bytes into an allocated buffer.
449    pub fn get_contract_state(&self, instance_id: &[u8; 32]) -> Option<Vec<u8>> {
450        #[cfg(target_family = "wasm")]
451        {
452            // Step 1: Get the state length
453            let len = unsafe {
454                __frnt__delegate__get_contract_state_len(instance_id.as_ptr() as i64, 32)
455            };
456            if len < 0 {
457                return None;
458            }
459            let len = len as usize;
460            if len == 0 {
461                return Some(Vec::new());
462            }
463
464            // Step 2: Read the state bytes
465            let mut buf = vec![0u8; len];
466            let read = unsafe {
467                __frnt__delegate__get_contract_state(
468                    instance_id.as_ptr() as i64,
469                    32,
470                    buf.as_mut_ptr() as i64,
471                    buf.len() as i64,
472                )
473            };
474            if read < 0 {
475                None
476            } else {
477                buf.truncate(read as usize);
478                Some(buf)
479            }
480        }
481        #[cfg(not(target_family = "wasm"))]
482        {
483            let _ = instance_id;
484            None
485        }
486    }
487
488    /// Store (PUT) contract state by instance ID.
489    ///
490    /// The contract's code must already be registered in the runtime's contract
491    /// store. Returns `true` on success, `false` on error.
492    pub fn put_contract_state(&mut self, instance_id: &[u8; 32], state: &[u8]) -> bool {
493        #[cfg(target_family = "wasm")]
494        {
495            let result = unsafe {
496                __frnt__delegate__put_contract_state(
497                    instance_id.as_ptr() as i64,
498                    32,
499                    state.as_ptr() as i64,
500                    state.len() as i64,
501                )
502            };
503            result == 0
504        }
505        #[cfg(not(target_family = "wasm"))]
506        {
507            let _ = (instance_id, state);
508            false
509        }
510    }
511
512    /// Update contract state by instance ID.
513    ///
514    /// Like `put_contract_state`, but only succeeds if the contract already has
515    /// stored state. This performs a full state replacement (not a delta-based
516    /// update through the contract's `update_state` logic). Returns `true` on
517    /// success, `false` if no prior state exists or on other errors.
518    pub fn update_contract_state(&mut self, instance_id: &[u8; 32], state: &[u8]) -> bool {
519        #[cfg(target_family = "wasm")]
520        {
521            let result = unsafe {
522                __frnt__delegate__update_contract_state(
523                    instance_id.as_ptr() as i64,
524                    32,
525                    state.as_ptr() as i64,
526                    state.len() as i64,
527                )
528            };
529            result == 0
530        }
531        #[cfg(not(target_family = "wasm"))]
532        {
533            let _ = (instance_id, state);
534            false
535        }
536    }
537
538    /// Subscribe to contract updates by instance ID.
539    ///
540    /// Registers interest in receiving notifications when the contract's state
541    /// changes. Currently validates that the contract is known and returns success;
542    /// actual notification delivery is a follow-up.
543    ///
544    /// Returns `true` on success, `false` if the contract is unknown or on error.
545    pub fn subscribe_contract(&mut self, instance_id: &[u8; 32]) -> bool {
546        #[cfg(target_family = "wasm")]
547        {
548            let result =
549                unsafe { __frnt__delegate__subscribe_contract(instance_id.as_ptr() as i64, 32) };
550            result == 0
551        }
552        #[cfg(not(target_family = "wasm"))]
553        {
554            let _ = instance_id;
555            false
556        }
557    }
558
559    /// Create a new child delegate from WASM bytecode and parameters.
560    ///
561    /// This V2 host function allows a delegate to spawn new delegates at runtime.
562    /// The child delegate is registered in the node's delegate store and secret store
563    /// with the provided cipher and nonce.
564    ///
565    /// Returns `Ok((key_hash, code_hash))` where both are 32-byte arrays identifying
566    /// the newly created delegate. Returns `Err(error_code)` on failure.
567    ///
568    /// # Resource Limits
569    /// - Maximum creation depth: 4 (prevents fork bombs)
570    /// - Maximum creations per process() call: 8
571    ///
572    /// # Error Codes
573    /// - `-1`: Called outside process() context
574    /// - `-4`: Invalid parameter
575    /// - `-9`: WASM memory bounds violation
576    /// - `-20`: Depth limit exceeded
577    /// - `-21`: Per-call creation limit exceeded
578    /// - `-23`: Invalid WASM module
579    /// - `-24`: Store registration failed
580    pub fn create_delegate(
581        &mut self,
582        wasm_code: &[u8],
583        params: &[u8],
584        cipher: &[u8; 32],
585        nonce: &[u8; 24],
586    ) -> Result<([u8; 32], [u8; 32]), i32> {
587        #[cfg(target_family = "wasm")]
588        {
589            let mut key_buf = [0u8; 32];
590            let mut hash_buf = [0u8; 32];
591            let result = unsafe {
592                __frnt__delegate__create_delegate(
593                    wasm_code.as_ptr() as i64,
594                    wasm_code.len() as i64,
595                    params.as_ptr() as i64,
596                    params.len() as i64,
597                    cipher.as_ptr() as i64,
598                    nonce.as_ptr() as i64,
599                    key_buf.as_mut_ptr() as i64,
600                    hash_buf.as_mut_ptr() as i64,
601                )
602            };
603            if result == 0 {
604                Ok((key_buf, hash_buf))
605            } else {
606                Err(result)
607            }
608        }
609        #[cfg(not(target_family = "wasm"))]
610        {
611            let _ = (wasm_code, params, cipher, nonce);
612            Err(error_codes::ERR_NOT_IN_PROCESS)
613        }
614    }
615}
616
617impl std::fmt::Debug for DelegateCtx {
618    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
619        f.debug_struct("DelegateCtx")
620            .field("context_len", &self.len())
621            .finish_non_exhaustive()
622    }
623}