Skip to main content

Storage Architecture

Savitri Network uses RocksDB as its primary storage backend with column families for data isolation and efficient access patterns.

Column Families

Column FamilyKeyValuePurpose
CF_BLOCKSheight (u64 LE)Block (bincode)Block storage
CF_TRANSACTIONStx_hash (32 bytes)Transaction (bincode)Transaction lookup
CF_ACCOUNTSaddress (32 bytes)Account (24 bytes)Balance + nonce
CF_METADATAstring keyvariesChain head, config
CF_RECEIPTStx_hashReceipt (bincode)Transaction receipts
CF_CONTRACTSaddressContractInfoSmart contract state
CF_GOVERNANCEproposal_idProposal (bincode)Governance proposals
CF_ORACLEfeed_idOracleDataOracle feeds
CF_BONDSaddressBond amountValidator bonds
CF_VOTE_TOKENSaddressVoteToken balanceGovernance voting tokens
CF_TREASURYkeyTreasury stateNetwork treasury
CF_VESTINGschedule_idVestingScheduleToken vesting
CF_REWARD_COINSaddressReward balanceNode rewards
CF_FEE_METRICSkeyFee dataFee tracking
CF_SUPPLY_METRICSkeySupply dataToken supply
CF_ACTIVE_NODESnode_idActivityNode liveness
CF_MONOLITHSmonolith_idMonolithBlock compression
CF_FLkeyFL dataFederated learning
CF_POUnode_idPoU scoreConsensus scores

Account Encoding

Accounts use a compact 24-byte fixed-width encoding:

Bytes 0-15:  balance (u128, little-endian)
Bytes 16-23: nonce (u64, little-endian)

Backward compatible: old 16-byte format (balance only, nonce=0) is still supported.

Empty accounts (balance=0, nonce=0) are never persisted to save storage space.

State Root

The state root is computed via lexicographic database snapshot:

seed  = H("STATEv1-LE")
leaf = H("STATE" || key || value) for each key-value pair
root = rolling_accumulate(seed, leaf_1, leaf_2, ..., leaf_n)

Keys are iterated in lexicographic order for determinism.

Storage Trait

All storage backends implement the StorageTrait:

pub trait StorageTrait: Send + Sync {
fn get_cf(&self, cf: &str, key: &[u8]) -> Result<Option<Vec<u8>>>;
fn put_cf(&self, cf: &str, key: &[u8], value: &[u8]) -> Result<()>;
fn delete_cf(&self, cf: &str, key: &[u8]) -> Result<()>;
fn get_cf_prefix(&self, cf: &str, prefix: &[u8]) -> Result<Vec<(Vec<u8>, Vec<u8>)>>;
// ... batch operations, iteration, etc.
}

Safe Deserialization

All bincode deserialization uses a maximum size limit to prevent memory exhaustion:

const MAX_BINCODE_SIZE: u64 = 16 * 1024 * 1024; // 16MB

Caching

LRU caching layer for frequently accessed data:

  • Account cache: reduces RocksDB reads for balance/nonce checks
  • Contract storage cache: caches SLOAD results within contract execution
  • Score cache: LRU cache for mempool TX scoring (avoids recomputation)

RocksDB Configuration

ParameterDevelopmentProduction
Cache size256 MB1024 MB
Write buffer16 MB64 MB
Max open files1001000
Sync interval5s30s
CompressionDisabledEnabled

Batch Operations

Atomic batch writes ensure consistency:

let mut batch = storage.batch();
batch.put_cf(CF_ACCOUNTS, &addr, &account.encode());
batch.put_cf(CF_TRANSACTIONS, &tx_hash, &tx_bytes);
batch.put_cf(CF_BLOCKS, &height_key, &block_bytes);
batch.commit()?;

Vesting System

Token vesting schedules support three types:

TypeBehavior
LinearTokens released linearly over duration
CliffNo tokens before cliff period, then linear
StagedGenesis compatibility mode

Feature Flags

FlagDescription
rocksdbRocksDB backend (default)
memoryIn-memory backend (testing)
prometheusPrometheus metrics export
tempfileTemporary storage for tests