Overview
The Storage struct is a wrapper around the sled embedded database, providing serialization helpers and key construction utilities for the Minichain blockchain.
Storage
Core database wrapper with automatic serialization/deserialization.
Constructor Methods
Opens a database at the specified path.pub fn open<P: AsRef<Path>>(path: P) -> Result<Self>
File system path where the database will be stored
Returns a Storage instance or StorageError on failure
Example:use minichain_storage::Storage;
let storage = Storage::open("./blockchain_data")?;
Opens an in-memory database for testing.pub fn open_temporary() -> Result<Self>
Returns a temporary Storage instance that will be deleted when dropped
Example:use minichain_storage::Storage;
let storage = Storage::open_temporary()?;
// Perfect for unit tests
Data Operations
Stores a serializable value with automatic encoding.pub fn put<K, V>(&self, key: K, value: &V) -> Result<()>
where
K: AsRef<[u8]>,
V: serde::Serialize
Key for storage (string or byte slice)
Value to serialize and store
Example:storage.put("balance", &1000u64)?;
storage.put(b"account:alice", &account)?;
Retrieves and deserializes a value.pub fn get<K, V>(&self, key: K) -> Result<Option<V>>
where
K: AsRef<[u8]>,
V: serde::de::DeserializeOwned
Returns Some(value) if found, None if missing, or error on deserialization failure
Example:let balance: Option<u64> = storage.get("balance")?;
match balance {
Some(b) => println!("Balance: {}", b),
None => println!("No balance found"),
}
Retrieves a value, returning error if not found.pub fn get_or_err<K, V>(&self, key: K) -> Result<V>
where
K: AsRef<[u8]> + std::fmt::Debug + Clone,
V: serde::de::DeserializeOwned
Key to retrieve (must be Debug + Clone for error reporting)
Returns the value or StorageError::NotFound
Example:// Fails with clear error if key doesn't exist
let balance: u64 = storage.get_or_err("balance")?;
Deletes a key from storage.pub fn delete<K: AsRef<[u8]>>(&self, key: K) -> Result<()>
Example:storage.delete("old_key")?;
Checks if a key exists in storage.pub fn contains<K: AsRef<[u8]>>(&self, key: K) -> Result<bool>
Example:if storage.contains("balance")? {
println!("Balance exists");
}
Batch Operations
Applies multiple operations atomically using sled’s write-ahead log.pub fn batch(&self, operations: Vec<BatchOp>) -> Result<()>
Vector of insert or remove operations
Example:use minichain_storage::BatchOp;
let ops = vec![
BatchOp::Insert {
key: b"key1".to_vec(),
value: bincode::serialize(&100u64)?,
},
BatchOp::Remove {
key: b"key2".to_vec(),
},
];
storage.batch(ops)?;
Flushes all pending writes to disk.pub fn flush(&self) -> Result<()>
Example:storage.flush()?; // Ensure durability
Key Construction Helpers
These methods generate standardized keys for different data types.
Creates a prefixed key for accounts.pub fn account_key(address: &Address) -> Vec<u8>
Format: "account:" + address_bytesExample:use minichain_core::Address;
let alice = Address([0xAA; 20]);
let key = Storage::account_key(&alice);
// key = b"account:\xAA\xAA\xAA..."
Storage::block_height_key
Creates a prefixed key for blocks by height.pub fn block_height_key(height: u64) -> Vec<u8>
Format: "block:height:{height}"Example:let key = Storage::block_height_key(42);
// key = b"block:height:42"
Creates a prefixed key for blocks by hash.pub fn block_hash_key(hash: &Hash) -> Vec<u8>
Format: "block:hash:" + hash_bytesExample:use minichain_core::Hash;
let hash = Hash([0xBB; 32]);
let key = Storage::block_hash_key(&hash);
Storage::contract_storage_key
Creates a prefixed key for contract storage slots.pub fn contract_storage_key(contract: &Address, slot: &[u8]) -> Vec<u8>
Format: "storage:" + contract_address + ":" + slotExample:let contract = Address([0xCC; 20]);
let slot = [0u8; 32];
let key = Storage::contract_storage_key(&contract, &slot);
Creates a prefixed key for contract code.pub fn code_key(code_hash: &Hash) -> String
Format: "code:{code_hash_hex}"Example:let code_hash = hash(b"contract bytecode");
let key = Storage::code_key(&code_hash);
// key = "code:a1b2c3..."
Advanced Access
Gets the underlying sled database for advanced operations.pub fn inner(&self) -> &Db
Example:// Use sled's scan_prefix for iteration
for result in storage.inner().scan_prefix(b"account:") {
let (key, value) = result?;
// Process each account...
}
BatchOp
Represents a single operation in an atomic batch.
pub enum BatchOp {
Insert { key: Vec<u8>, value: Vec<u8> },
Remove { key: Vec<u8> },
}
Variants
Inserts or updates a key-value pairPre-serialized value bytes
StorageError
Errors that can occur during storage operations.
pub enum StorageError {
Database(sled::Error),
Serialization(bincode::Error),
NotFound(String),
InsufficientBalance { address: Address, required: u64, available: u64 },
InvalidGenesis(String),
InvalidStorageValue,
}
Variants
Underlying sled database error
Bincode serialization/deserialization error
Key not found in database
Account doesn’t have enough balance for operation
Genesis block validation error
Contract storage value has invalid format
Result Type
pub type Result<T> = std::result::Result<T, StorageError>;
Convenience type alias for storage operations.
Complete Example
use minichain_storage::{Storage, BatchOp};
use minichain_core::Address;
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Open database
let storage = Storage::open("./my_blockchain")?;
// Store some data
let alice = Address([0xAA; 20]);
storage.put(Storage::account_key(&alice), &1000u64)?;
// Retrieve it
let balance: u64 = storage.get_or_err(Storage::account_key(&alice))?;
println!("Alice's balance: {}", balance);
// Batch operations
let ops = vec![
BatchOp::Insert {
key: b"key1".to_vec(),
value: bincode::serialize(&42u64)?,
},
BatchOp::Insert {
key: b"key2".to_vec(),
value: bincode::serialize(&100u64)?,
},
];
storage.batch(ops)?;
// Flush to disk
storage.flush()?;
Ok(())
}
See Also