Aspen now uses iroh-blobs for efficient VM binary storage, eliminating JSON/base64 overhead and enabling P2P distribution.
Binary (52KB) → Base64 (70KB) → JSON → Postcard → Network
Binary (52KB) → Blob Store → BLAKE3 Hash → Network (just 32-byte hash!)
- 33% bandwidth reduction - No base64 encoding overhead
- P2P distribution - Nodes fetch binaries from each other via iroh-blobs protocol
- Automatic deduplication - Same binary = same hash = stored once
- Content-addressed caching - BLAKE3 hashes ensure integrity
- Efficient streaming - Large binaries can be streamed, not loaded in memory
use aspen_blob::BlobStore;
// Get blob store from node
let blob_store = node.blob_store().unwrap();
// Upload binary
let binary = std::fs::read("path/to/binary")?;
let result = blob_store.add_bytes(&binary).await?;
let hash = result.blob_ref.hash.to_string();
let size = result.blob_ref.size;use aspen_jobs::JobSpec;
// Create job spec with blob hash (NOT the binary itself!)
let job_spec = JobSpec::with_blob_binary(hash, size, "elf")
.timeout(Duration::from_secs(10))
.tag("my-vm-job");// The payload now contains just the hash reference
ClientRpcRequest::JobSubmit {
job_type: "vm_execute".to_string(),
payload: serde_json::json!({
"type": "BlobBinary",
"hash": hash,
"size": size,
"format": "elf"
}),
// ... other fields
}The HyperlightWorker automatically:
- Receives job with blob hash
- Retrieves binary from local blob store
- If not found locally, fetches from peer nodes via iroh-blobs protocol
- Executes in Hyperlight VM
- Caches the blob hash for future use
// 1. Start node with blob storage
let node = NodeBuilder::new()
.enable_blob_storage()
.build()
.await?;
// 2. Upload binary
let blob_store = node.blob_store().unwrap();
let binary = std::fs::read("echo-worker")?;
let result = blob_store.add_bytes(&binary).await?;
// 3. Submit job
let job_spec = JobSpec::with_blob_binary(
result.blob_ref.hash.to_string(),
result.blob_ref.size,
"elf"
);
// 4. VM executes with binary fetched from blob storeAll VM binaries must be uploaded to blob storage first, then referenced by hash:
// Store binary in blobs first
let result = blob_store.add_bytes(&binary_data).await?;
// Create job spec with blob reference
let job_spec = JobSpec::with_blob_binary(
result.blob_ref.hash.to_string(),
result.blob_ref.size,
"elf"
);The JobPayload enum supports three variants:
BlobBinary { hash, size, format }- VM binary stored in blob storeNixExpression { flake_url, attribute }- Nix flake referenceNixDerivation { content }- Inline Nix derivation
When a node needs a binary:
- Check local store - O(1) lookup by hash
- Query cluster peers - "Who has hash X?"
- Fetch via iroh-blobs - Direct QUIC transfer
- Cache locally - Future jobs use local copy
- Upload once: Binary uploaded once, referenced many times
- Network efficiency: 52KB binary → 32-byte hash in job payload
- Deduplication: 100 jobs with same binary = 1 blob stored
- P2P speedup: Parallel downloads from multiple peers
- Integrity: BLAKE3 hash ensures binary hasn't been tampered
- No trust needed: Content-addressing means hash IS the identity
- Efficient validation: Hash checked on retrieval
# Upload binary to cluster
aspen blob add echo-worker
# Returns: Hash: 3b99a8d846f3d553...
# Submit VM job with hash
aspen job submit \
--type vm_execute \
--binary-hash 3b99a8d846f3d553... \
--input "Hello, World!"Check if binary exists in blob store:
let exists = blob_store.get_bytes(&hash).await?.is_some();List protected blobs (won't be garbage collected):
let tags = blob_store.list_protected().await?;- Upload binaries once - Store hash for reuse
- Use protection tags - Prevent GC of important binaries
- Validate size - Include size in BlobBinary for validation
- Format hints - Specify "elf", "wasm", etc. for clarity
- Error handling - Handle "blob not found" gracefully