Dolos is a lightweight Cardano node designed specifically for keeping an updated copy of the ledger and responding to queries from trusted clients while requiring minimal resources compared to a full node. It serves as a data provider rather than a consensus-validating node, focusing on efficiency and compatibility with existing Cardano ecosystem tools.
Dolos uses four distinct storage backends, each serving a specific purpose:
- Purpose: Current ledger state (the "world view")
- Contents: UTxO set, entity state, chain cursor position
- Traits:
StateStore(reads) +StateWriter(batched writes) - Database:
<storage.path>/state
- Purpose: Historical block storage with temporal indexing
- Contents: Raw blocks indexed by slot, entity logs keyed by
LogKey(slot + entity key) - Traits:
ArchiveStore(reads) +ArchiveWriter(batched writes) - Database:
<storage.path>/chain
- Purpose: Crash recovery and rollback support
- Contents: Log entries with block data, entity deltas, and input UTxOs
- Traits:
WalStore - Database:
<storage.path>/wal
- Purpose: Cross-cutting indexes for fast lookups
- Contents: Two types of indexes:
- UTxO Filter Indexes: Current state queries (by address, payment, stake, policy, asset)
- Archive Indexes: Historical queries (by block hash, tx hash, slots with address/asset/etc.)
- Traits:
IndexStore(reads) +IndexWriter(batched writes) - Database:
<storage.path>/index(isolated from other stores) - Design Note: Returns primitive values (slots, UTxO refs) not block data. Use
QueryHelpersto join with archive for full data.
<storage.path>/
├── wal # Write-Ahead Log database
├── state # Ledger state database
├── chain # Archive/block storage database
└── index # Consolidated index database
Each database is a separate Redb or Fjall file with independent configuration for cache size and durability, depending on the chosen storage backend.
The project follows a modular workspace architecture with clear separation of concerns and trait-based extensibility.
- Purpose: Main application binary and CLI interface
- Role: Application layer that orchestrates all services
- Key Modules:
sync: Chain synchronization from upstream nodesserve: gRPC (UTxO RPC) and Ouroboros network servicesrelay: Upstream relay connection handlingmempool: Transaction mempool implementationfacade: High-level domain operations (extendsdolos-corefacade)
- CLI Commands: daemon, sync, serve, bootstrap (relay/mithril/snapshot), data, doctor
- Features: Configurable service compilation (grpc, minibf, trp, mithril, utils)
- Purpose: Core traits, types, and abstractions common to all Dolos components
- Key Modules:
state:StateStoreandStateWritertraits, entity systemarchive:ArchiveStoreandArchiveWritertraits,SlotTagsfor indexing metadataindexes:IndexStoreandIndexWritertraits for cross-cutting indexeswal:WalStoretrait for write-ahead loggingbatch:WorkBatch,WorkBlock,WorkDeltasfor batch processing pipelinefacade: High-level operations (execute_batch,roll_forward,import_blocks)query:QueryHelperstrait andSparseBlockIterfor joining indexes with archiveDomain: Central trait tying all storage backends togetherChainLogic: Trait for blockchain-specific processing logicmempool: Transaction mempool interface
- Role: Foundation layer providing the architecture that other crates implement
- Purpose: Cardano-specific implementation of the core traits
- Components:
CardanoLogic: Implementation ofChainLogicfor CardanoCardanoEntity/CardanoDelta: Entity-delta implementations- Block processing, validation, and era handling
- Genesis configuration management and bootstrap
- Reward distribution processing
- UTxO set delta computation
- Dependencies:
dolos-core, Pallas library for Cardano protocol support
- Purpose: Storage backend implementation using the Redb v3 embedded database
- Components:
state:StateStoreimplementation with UTxO storage and entity tablesarchive:ArchiveStoreimplementation with block and log storagewal:WalStoreimplementation for crash recoveryindexes:IndexStoreimplementation (isolated database) with:- UTxO filter indexes (by address, payment, stake, policy, asset)
- Archive indexes (by block hash, tx hash, address, asset, datum, etc.)
- Role: Persistence layer implementing the core storage traits
- Purpose: Alternative storage backend implementation using the Fjall LSM-tree embedded database
- Design Philosophy: Optimized for write-heavy workloads with many keys, ideal for blockchain data
- Components:
state:StateStoreimplementation with three-keyspace design:state-cursor: Chain position tracking (single key-value)state-utxos: UTxO set storage with[tx_hash:32][index:4]keysstate-entities: All entity types with[ns_hash:8][entity_key:32]keys
index:IndexStoreimplementation with three-keyspace design:index-cursor: Chain position trackingindex-exact: Exact-match lookups with[dim_hash:8][key_data:var]->[slot:8]index-tags: Tag-based prefix scans for UTxO and block tags
keys: Shared key encoding utilities
- Key Advantages:
- Reduced segment files compared to per-entity keyspaces
- Chain-agnostic design using dimension hashing
- LSM-tree optimization for high-write blockchain workloads
- Role: Alternative persistence layer implementing
StateStoreandIndexStoretraits
- Purpose: Blockfrost-compatible HTTP API service
- Components:
- REST API endpoints mimicking Blockfrost
- Cardano data mapping and transformation
- HTTP server using Axum framework
- Role: API compatibility layer for existing Blockfrost clients
- Purpose: Transaction Resolver Protocol implementation (Tx3 framework integration)
- Components:
- JSON-RPC server for transaction processing
- Integration with Tx3 SDK for transaction resolution
- Role: Transaction processing service leveraging the Tx3 framework
- Purpose: Testing utilities and mock implementations
- Features:
ToyDomain: Minimal in-memoryDomainimplementation for testing- Test data generation (fake UTxOs, blocks, deltas)
- Test address utilities
- Role: Development and testing support
- Purpose: Development task automation following cargo-xtask pattern
- Role: Build scripts and development utilities
dolos (main binary)
├── dolos-core (foundation)
├── dolos-cardano (Cardano logic) → dolos-core
├── dolos-redb3 (storage) → dolos-core
├── dolos-fjall (storage) → dolos-core
├── dolos-minibf (API) → dolos-core + dolos-cardano
├── dolos-trp (TX resolver) → dolos-core + dolos-cardano (Tx3 integration)
└── dolos-testing (dev) → dolos-core + dolos-cardano + dolos-redb3
- Core Layer (
dolos-core): Abstract traits and interfaces - Implementation Layer (
dolos-cardano,dolos-redb3,dolos-fjall): Concrete implementations - Service Layer (
dolos-minibf,dolos-trp): API services - Application Layer (
dolos): Main binary and CLI
Dolos processes blockchain data through a pipeline of work units. Each work unit functions as a mini-ETL job that extracts data from storage, transforms it using chain-specific logic, and loads results into the appropriate stores (state, archive, index).
The WorkUnit<D: Domain> trait (dolos-core/src/work_unit.rs) defines the contract for all processing units. An executor component manages each work unit's lifecycle by calling methods in a specific sequence:
load()- Extract required data from storage (UTxOs, entities)compute()- Perform chain-specific transformationscommit_wal()- Write to WAL for crash recoverycommit_state()- Persist state changes to StateStorecommit_archive()- Persist block data to ArchiveStorecommit_indexes()- Update IndexStore
The executor implementations live in dolos-core/src/sync.rs (full lifecycle) and dolos-core/src/import.rs (bulk import, skips WAL).
The CardanoWorkUnit enum (dolos-cardano/src/lib.rs) defines Cardano-specific work unit variants:
GenesisWorkUnit- Bootstrap chain from genesis configurationRollWorkUnit- Process block batches (primary work unit)RupdWorkUnit- Compute rewards at stability windowEwrapWorkUnit- Apply computed rewards at epoch endEstartWorkUnit- Handle era transitions at epoch start
Each variant implements WorkUnit and determines which stores it modifies during its commit phases.
The Domain trait is the central abstraction that ties all components together:
pub trait Domain: Send + Sync + Clone + 'static {
type Entity: Entity;
type EntityDelta: EntityDelta<Entity = Self::Entity>;
type Chain: ChainLogic<Delta = Self::EntityDelta, Entity = Self::Entity>;
type Wal: WalStore<Delta = Self::EntityDelta>;
type State: StateStore;
type Archive: ArchiveStore;
type Indexes: IndexStore;
type Mempool: MempoolStore;
type TipSubscription: TipSubscription;
fn wal(&self) -> &Self::Wal;
fn state(&self) -> &Self::State;
fn archive(&self) -> &Self::Archive;
fn indexes(&self) -> &Self::Indexes;
fn mempool(&self) -> &Self::Mempool;
// ... configuration and chain access methods
}All storage traits follow a consistent pattern for batched, atomic writes:
// 1. Start a writer (begins transaction)
let writer = store.start_writer()?;
// 2. Perform multiple operations
writer.apply_something(&data)?;
writer.apply_another(&more_data)?;
// 3. Commit atomically (consumes the writer)
writer.commit()?;This pattern is used by:
StateStore→StateWriterArchiveStore→ArchiveWriterIndexStore→IndexWriter
State mutations use a reversible delta pattern:
pub trait EntityDelta {
type Entity: Entity;
fn key(&self) -> NsKey; // Namespace + key
fn apply(&mut self, entity: &mut Option<Self::Entity>); // Forward application
fn undo(&self, entity: &mut Option<Self::Entity>); // Rollback
}- Entities are keyed by
NsKey(Namespace, EntityKey) - Deltas describe changes, not final states
apply()can store "before" values for laterundo()- Enables efficient rollbacks without full state snapshots
The QueryHelpers trait (auto-implemented for all Domain types) joins index lookups with archive fetches:
// Index returns slots, QueryHelpers fetches the actual blocks
fn blocks_with_address(&self, address, start, end) -> SparseBlockIter;SparseBlockIter is lazy - it only fetches blocks from archive when iterated, enabling efficient pagination and early termination.
ChainLogictrait allows different blockchain implementationsStateStore,ArchiveStore,IndexStore,WalStorefor storage componentsMempoolStorefor transaction mempool- Service feature flags enable modular functionality
- Lightweight Architecture: Intentionally avoids full consensus validation for minimal resource usage
- Trust Model: Relies on trusted upstream peers rather than independent validation
- Separate Index Database: Indexes live in their own database file (
index) for independent scaling, tuning, and rebuilding without touching primary data - Primitive-Value Indexes: Index queries return slots/refs rather than full data; join with archive separately via
QueryHelpers - Batched Writes: All storage writes go through transactional writers for atomicity and performance
- Entity-Delta System: State changes are represented as reversible deltas for efficient rollbacks
- Parallel Processing: Batch operations use Rayon for parallel UTxO decoding and entity loading
- Modular Services: Different API endpoints (gRPC, Blockfrost, TRP) can be enabled/disabled via features
- Future Extensibility: Architecture supports planned P2P features and light consensus validation
This crate organization enables Dolos to serve as a lightweight, efficient Cardano data node while maintaining flexibility for different use cases and future enhancements.
All agents working on this repository must verify their modifications by running the following checks before considering any changes complete:
-
Clippy Linting: Run
cargo clippyand ensure no warnings appearcargo clippy --workspace --all-targets --all-features
-
Clean Build: Ensure the project builds without warnings
cargo build --workspace --all-targets --all-features
-
Testing: Run tests to verify functionality
cargo test --workspace --all-features
- All warnings from
cargo clippymust be resolved before committing changes - Code should follow existing Rust conventions and patterns established in the codebase
- New implementations should follow the trait-based architecture patterns
- Storage implementations should maintain consistency between backends (redb3 and fjall)
These verification steps ensure code quality, maintain consistency across storage backends, and prevent introducing technical debt into the codebase.