use calimero_node_primitives::sync::TreeLeafData;
use calimero_primitives::application::ApplicationId;
use calimero_primitives::context::ContextId;
use calimero_storage::address::Id;
use calimero_storage::entities::{ChildInfo, Metadata};
use calimero_storage::index::Index;
use calimero_storage::interface::{Action, Interface};
use calimero_storage::store::MainStorage;
use eyre::{bail, Result};
use rand::Rng;
#[allow(dead_code, reason = "utility function for application validation")]
pub fn validate_application_id(ours: &ApplicationId, theirs: &ApplicationId) -> eyre::Result<()> {
if ours != theirs {
bail!("application mismatch: expected {}, got {}", ours, theirs);
}
Ok(())
}
#[must_use]
pub fn generate_nonce() -> calimero_crypto::Nonce {
rand::thread_rng().gen()
}
pub fn apply_leaf_with_crdt_merge(context_id: ContextId, leaf: &TreeLeafData) -> Result<()> {
let entity_id = Id::new(leaf.key);
let root_id = Id::new(*context_id.as_ref());
let existing_index = Index::<MainStorage>::get_index(entity_id).ok().flatten();
let mut metadata = Metadata::default();
metadata.crdt_type = Some(leaf.metadata.crdt_type.clone());
metadata.updated_at = leaf.metadata.hlc_timestamp.into();
let action = if existing_index.is_some() {
Action::Update {
id: entity_id,
data: leaf.value.clone(),
ancestors: vec![], metadata,
}
} else {
if Index::<MainStorage>::get_index(root_id)
.ok()
.flatten()
.is_none()
{
let root_action = Action::Update {
id: root_id,
data: vec![],
ancestors: vec![],
metadata: Metadata::default(),
};
Interface::<MainStorage>::apply_action(root_action)?;
}
let root_hash = Index::<MainStorage>::get_hashes_for(root_id)
.ok()
.flatten()
.map(|(full, _)| full)
.unwrap_or([0; 32]);
let root_metadata = Index::<MainStorage>::get_index(root_id)
.ok()
.flatten()
.map(|idx| idx.metadata.clone())
.unwrap_or_default();
let ancestor = ChildInfo::new(root_id, root_hash, root_metadata);
Action::Add {
id: entity_id,
data: leaf.value.clone(),
ancestors: vec![ancestor],
metadata,
}
};
Interface::<MainStorage>::apply_action(action)?;
Ok(())
}
pub const MAX_ENTITIES_PER_PUSH: usize = 500;
pub fn handle_entity_push(
runtime_env: &calimero_storage::env::RuntimeEnv,
context_id: ContextId,
entities: &[TreeLeafData],
) -> u32 {
let entities = if entities.len() > MAX_ENTITIES_PER_PUSH {
tracing::warn!(
%context_id,
received = entities.len(),
max = MAX_ENTITIES_PER_PUSH,
"EntityPush exceeds max, truncating"
);
&entities[..MAX_ENTITIES_PER_PUSH]
} else {
entities
};
calimero_storage::env::with_runtime_env(runtime_env.clone(), || {
let mut applied = 0u32;
for leaf in entities {
match apply_leaf_with_crdt_merge(context_id, leaf) {
Ok(()) => applied += 1,
Err(e) => {
tracing::warn!(
%context_id,
key = %hex::encode(leaf.key),
error = %e,
"Failed to apply pushed entity"
);
}
}
}
applied
})
}
pub fn extract_signed_op_bytes(skeleton_bytes: &[u8]) -> Option<Vec<u8>> {
use calimero_context_client::local_governance::{SignedNamespaceOp, StoredNamespaceEntry};
if let Ok(StoredNamespaceEntry::Signed(op)) =
borsh::from_slice::<StoredNamespaceEntry>(skeleton_bytes)
{
return borsh::to_vec(&op).ok();
}
if borsh::from_slice::<SignedNamespaceOp>(skeleton_bytes).is_ok() {
return Some(skeleton_bytes.to_vec());
}
None
}
#[cfg(test)]
mod tests {
use super::*;
use calimero_primitives::application::ApplicationId;
#[test]
fn test_validate_application_id_matching() {
let app_id = ApplicationId::from([1u8; 32]);
assert!(validate_application_id(&app_id, &app_id).is_ok());
}
#[test]
fn test_validate_application_id_mismatch() {
let app1 = ApplicationId::from([1u8; 32]);
let app2 = ApplicationId::from([2u8; 32]);
let result = validate_application_id(&app1, &app2);
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("application mismatch"));
}
#[test]
fn test_generate_nonce_returns_value() {
let nonce = generate_nonce();
assert_ne!(nonce, [0u8; 12]);
}
#[test]
fn test_generate_nonce_is_random() {
let nonce1 = generate_nonce();
let nonce2 = generate_nonce();
assert_ne!(nonce1, nonce2, "Nonces should be randomly generated");
}
}