Store and share a blob
Task: store a large binary (a file, an image) and make it available to other nodes in the context.
Blobs live outside CRDT state: you store the bytes once, get back a content
BlobId, keep that small id in your state, and announce the blob so peers can
fetch it. The replication model is in the
blobs reference.
The blob flow
Section titled “The blob flow”The SDK exposes a file-descriptor-style API in calimero_sdk::env:
| Step | Function | Returns |
|---|---|---|
| Open a new blob for writing | blob_create() |
fd (u64) |
| Append bytes | blob_write(fd, &data) |
bytes written |
| Finish writing | blob_close(fd) |
BlobId ([u8; 32]) |
| Open an existing blob | blob_open(&blob_id) |
fd |
| Read bytes | blob_read(fd, &mut buf) |
bytes read |
Write path: blob_create → blob_write… → blob_close (the returned id is the
content hash). Read path: blob_open → blob_read… → blob_close. blob_open
opens only a blob held for the current context: the app wrote it, it was
uploaded with that context_id on a member node, it was fetched from the
context’s peers, or it is the context’s own application. Anything else traps.
To read a blob another node produced, use blob_open_in_context, which fetches
it from the context’s peers when this node does not hold it for the context.
Announce so peers can fetch
Section titled “Announce so peers can fetch”Storing a blob keeps it local. env::blob_announce_to_context(&blob_id, &context_id) advertises it to the rest of the context so other nodes can
discover and download it. This is apps/blobs:
use calimero_sdk::{app, env, AccountId, BlobId};
pub fn upload_file(&mut self, name: String, blob_id: BlobId, size: u64) -> app::Result<String> { let current_context = env::context_id();
if env::blob_announce_to_context(blob_id.as_ref(), ¤t_context) { app::log!("Announced blob {} to network", blob_id); } else { app::log!("Warning: Failed to announce blob {}", blob_id); }
// keep the small id + metadata in CRDT state; the bytes stay in the blob store. // The id names the uploader and carries a nonce: a shared counter would let // two nodes mint the same id at once. `files` is an `AuthoredMap`, so the // record is the uploader's own entry. let mut nonce = [0u8; 8]; env::random_bytes(&mut nonce); let file_id = format!("{}_{}", AccountId::from(env::account_id()), hex(&nonce)); self.files.insert(file_id.clone(), /* FileRecord { blob_id, name, size, .. } */ )?; Ok(file_id)}The full arc: write the bytes to get a content id, announce that id so the rest
of the context learns the blob exists, then a peer’s app opens it by id with
blob_open_in_context, which fetches and streams the bytes, recomputing the id
to confirm it got what it asked for.
Exercise it
Section titled “Exercise it”# the BlobId comes back hex-encoded; store the metadata via your methodmeroctl --node node1 call upload_file \ --context <context-id> \ --args '{"name": "notes.txt", "blob_id": "<hex-blob-id>", "size": 12}'