Skip to main content

calimero_node_primitives/sync/
storage_bridge.rs

1//! Storage bridge utilities for sync protocols.
2//!
3//! This module provides helpers to bridge `calimero-storage` APIs (which use
4//! the `RuntimeEnv` thread-local) to the underlying `calimero-store` backend.
5//!
6//! # Why This Exists
7//!
8//! The `calimero-storage` crate provides high-level storage APIs (`Index`, `Interface`)
9//! that operate through a thread-local `RuntimeEnv`. This `RuntimeEnv` contains
10//! callbacks that route read/write/remove operations to the actual database.
11//!
12//! This module provides a single, reusable way to create these callbacks from
13//! a `Store`, regardless of the backend (RocksDB or InMemoryDB).
14//!
15//! # Usage
16//!
17//! ```ignore
18//! use calimero_node_primitives::sync::storage_bridge::create_runtime_env;
19//!
20//! // Works with any Store backend (RocksDB or InMemoryDB)
21//! let runtime_env = create_runtime_env(&store, context_id, identity);
22//!
23//! // Use with storage APIs
24//! with_runtime_env(runtime_env, || {
25//!     let index = Index::<MainStorage>::get_index(entity_id)?;
26//!     // ...
27//! });
28//! ```
29
30use std::cell::RefCell;
31use std::rc::Rc;
32
33use calimero_primitives::context::ContextId;
34use calimero_primitives::identity::PublicKey;
35use calimero_storage::env::RuntimeEnv;
36use calimero_storage::store::Key;
37use calimero_store::{key, types, Store};
38use tracing::warn;
39
40/// Create a `RuntimeEnv` that bridges `calimero-storage` to a `Store`.
41///
42/// This is the canonical way to set up storage access for sync protocols.
43/// The returned `RuntimeEnv` can be used with `with_runtime_env()` to enable
44/// `Index<MainStorage>` and `Interface<MainStorage>` operations.
45///
46/// # Arguments
47///
48/// * `store` - The underlying store (works with both RocksDB and InMemoryDB)
49/// * `context_id` - The context being accessed
50/// * `executor_id` - The identity executing operations
51///
52/// # Example
53///
54/// ```ignore
55/// let env = create_runtime_env(&store, context_id, identity);
56/// let result = with_runtime_env(env, || {
57///     Index::<MainStorage>::get_index(entity_id)
58/// });
59/// ```
60pub fn create_runtime_env(
61    store: &Store,
62    context_id: ContextId,
63    executor_id: PublicKey,
64) -> RuntimeEnv {
65    let callbacks = create_storage_callbacks(store, context_id);
66    RuntimeEnv::new(
67        callbacks.read,
68        callbacks.write,
69        callbacks.remove,
70        *context_id.as_ref(),
71        *executor_id.as_ref(),
72    )
73}
74
75/// Storage callback closures that bridge `calimero-storage` Key API to the Store.
76///
77/// These closures translate `calimero-storage::Key` (Index/Entry) to
78/// `calimero-store::ContextStateKey` for access to the actual database.
79#[expect(
80    clippy::type_complexity,
81    reason = "Matches RuntimeEnv callback signatures"
82)]
83struct StorageCallbacks {
84    read: Rc<dyn Fn(&Key) -> Option<Vec<u8>>>,
85    write: Rc<dyn Fn(Key, &[u8]) -> bool>,
86    remove: Rc<dyn Fn(&Key) -> bool>,
87}
88
89/// Create storage callbacks for a context.
90///
91/// These bridge the `calimero-storage` Key-based API to the underlying
92/// `calimero-store` ContextStateKey-based storage.
93#[expect(
94    clippy::type_complexity,
95    reason = "Matches RuntimeEnv callback signatures"
96)]
97fn create_storage_callbacks(store: &Store, context_id: ContextId) -> StorageCallbacks {
98    let read: Rc<dyn Fn(&Key) -> Option<Vec<u8>>> = {
99        let handle = store.handle();
100        let ctx_id = context_id;
101        Rc::new(move |key: &Key| {
102            let storage_key = key.to_bytes();
103            let state_key = key::ContextState::new(ctx_id, storage_key);
104            match handle.get(&state_key) {
105                Ok(Some(state)) => Some(state.value.into_boxed().into_vec()),
106                Ok(None) => None,
107                Err(e) => {
108                    warn!(
109                        %ctx_id,
110                        storage_key = %hex::encode(storage_key),
111                        error = ?e,
112                        "Storage read failed"
113                    );
114                    None
115                }
116            }
117        })
118    };
119
120    let write: Rc<dyn Fn(Key, &[u8]) -> bool> = {
121        let handle_cell: Rc<RefCell<_>> = Rc::new(RefCell::new(store.handle()));
122        let ctx_id = context_id;
123        Rc::new(move |key: Key, value: &[u8]| {
124            let storage_key = key.to_bytes();
125            let state_key = key::ContextState::new(ctx_id, storage_key);
126            let slice: calimero_store::slice::Slice<'_> = value.to_vec().into();
127            let state_value = types::ContextState::from(slice);
128            handle_cell
129                .borrow_mut()
130                .put(&state_key, &state_value)
131                .is_ok()
132        })
133    };
134
135    let remove: Rc<dyn Fn(&Key) -> bool> = {
136        let handle_cell: Rc<RefCell<_>> = Rc::new(RefCell::new(store.handle()));
137        let ctx_id = context_id;
138        Rc::new(move |key: &Key| {
139            let storage_key = key.to_bytes();
140            let state_key = key::ContextState::new(ctx_id, storage_key);
141            handle_cell.borrow_mut().delete(&state_key).is_ok()
142        })
143    };
144
145    StorageCallbacks {
146        read,
147        write,
148        remove,
149    }
150}
151
152#[cfg(test)]
153mod tests {
154    use std::sync::Arc;
155
156    use super::*;
157    use calimero_storage::env::with_runtime_env;
158    use calimero_storage::index::Index;
159    use calimero_storage::store::MainStorage;
160    use calimero_store::db::InMemoryDB;
161
162    #[test]
163    fn test_create_runtime_env_with_inmemory() {
164        // Create an in-memory store
165        let db = InMemoryDB::owned();
166        let store = Store::new(Arc::new(db));
167
168        // Create a context ID and identity
169        let context_id = ContextId::from([1u8; 32]);
170        let identity = PublicKey::from([2u8; 32]);
171
172        // Create RuntimeEnv - should not panic
173        let env = create_runtime_env(&store, context_id, identity);
174
175        // Use it with storage APIs
176        let result = with_runtime_env(env, || {
177            // Try to get a non-existent index - should return None, not panic
178            Index::<MainStorage>::get_index(calimero_storage::address::Id::root())
179        });
180
181        // Root index doesn't exist yet, should be Ok(None)
182        assert!(result.is_ok());
183    }
184}