Skip to content

Commit 9a63759

Browse files
committed
feat: add inkwell crate
1 parent 3e4a2fe commit 9a63759

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

61 files changed

+7608
-1937
lines changed

CLAUDE.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ cargo run -p ledger-server --release -- --config config.toml
7575
│ Raft via openraft │ Log storage │ Batching │ Idempotency │
7676
├─────────────────────────────────────────────────────────────┤
7777
│ ledger-storage (state) │
78-
redb engine │ Entity/Relationship stores │ Indexes
78+
inkwell B+ tree │ Entity/Relationship stores │ Indexes │
7979
├─────────────────────────────────────────────────────────────┤
8080
│ ledger-types (shared) │
8181
│ Hash primitives │ Merkle proofs │ Config │ Error types │
@@ -85,13 +85,13 @@ cargo run -p ledger-server --release -- --config config.toml
8585
**Crates:**
8686

8787
- `ledger-types` — Core types, SHA-256/seahash, merkle tree, snafu errors
88-
- `ledger-storage`redb wrapper, entity/relationship CRUD, dual indexes, state root computation
88+
- `ledger-storage`inkwell wrapper, entity/relationship CRUD, dual indexes, state root computation
8989
- `ledger-raft` — openraft integration, log storage, gRPC services, transaction batching
9090
- `ledger-server` — Main binary, bootstrap, config loading
9191

9292
**Key abstractions:**
9393

94-
- `StorageEngine` (storage/engine.rs) — redb database wrapper with transaction helpers
94+
- `StorageEngine` (storage/engine.rs) — inkwell database wrapper with transaction helpers
9595
- `StateLayer` (storage/state.rs) — Applies blocks, computes bucket-based state roots
9696
- `LedgerServer` (raft/server.rs) — gRPC server combining all services with Raft
9797

Cargo.lock

Lines changed: 33 additions & 12 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ members = [
55
"crates/storage",
66
"crates/raft",
77
"crates/server",
8+
"crates/inkwell",
89
]
910

1011
[workspace.package]
@@ -29,9 +30,6 @@ tower = { version = "0.5", features = ["limit", "load-shed", "timeout"] }
2930
# Raft consensus
3031
openraft = { version = "0.9", features = ["serde", "tracing-log"] }
3132

32-
# Storage
33-
redb = "2.4"
34-
3533
# Crypto
3634
sha2 = "0.10"
3735
seahash = "4.1"
@@ -106,6 +104,12 @@ async-stream = "0.3"
106104
ledger-types = { path = "crates/types" }
107105
ledger-storage = { path = "crates/storage" }
108106
ledger-raft = { path = "crates/raft" }
107+
inkwell = { path = "crates/inkwell" }
108+
109+
# Hashing (for inkwell)
110+
xxhash-rust = { version = "0.8", features = ["xxh32", "xxh3"] }
111+
byteorder = "1.5"
112+
memmap2 = "0.9"
109113

110114
[workspace.lints.rust]
111115
unsafe_code = "deny"

DESIGN.md

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ org namespace (per-organization)
8181
- [Durability & Finality Model](#durability--finality-model)
8282
- [Persistent Storage Architecture](#persistent-storage-architecture)
8383
- [Directory Layout](#directory-layout)
84-
- [Storage Backend: redb](#storage-backend-redb)
84+
- [Storage Backend: inkwell](#storage-backend-inkwell)
8585
- [Block Archive Format](#block-archive-format)
8686
- [Snapshot Format](#snapshot-format)
8787
- [Crash Recovery](#crash-recovery)
@@ -394,7 +394,7 @@ graph TD
394394

395395
**What's NOT merkleized per-write:**
396396

397-
- Individual index entries (stored in redb for O(1) lookup)
397+
- Individual index entries (stored in inkwell for O(1) lookup)
398398
- Dual indexes for relationship traversal
399399

400400
**Verification approach:**
@@ -405,7 +405,7 @@ graph TD
405405

406406
```rust
407407
struct StateLayer {
408-
// Fast K/V storage for queries (redb)
408+
// Fast K/V storage for queries (inkwell)
409409
kv: Database,
410410

411411
// Indexes for relationship traversal
@@ -550,7 +550,7 @@ fn decode_storage_key(storage_key: &[u8]) -> (VaultId, u8, &[u8]) {
550550

551551
#### Per-Vault Bucket Structure
552552

553-
Each vault maintains independent bucket tracking. Vaults in the same shard share a redb instance but compute separate `state_root` values.
553+
Each vault maintains independent bucket tracking. Vaults in the same shard share an inkwell database but compute separate `state_root` values.
554554

555555
```rust
556556
struct ShardState {
@@ -655,7 +655,7 @@ impl ShardState {
655655
State root computation must be identical on all nodes:
656656

657657
1. **Hash function**: seahash for bucket assignment, SHA-256 for roots
658-
2. **Iteration order**: redb guarantees lexicographic key order within range
658+
2. **Iteration order**: inkwell guarantees lexicographic key order within range
659659
3. **Key-value encoding**: Length-prefixed (prevents ambiguous concatenation)
660660
4. **Empty bucket**: Hash of empty input → `SHA-256("")`
661661

@@ -1590,7 +1590,7 @@ Scaling dimensions:
15901590

15911591
| Metric | Target | Measurement | Rationale |
15921592
| ------------------ | --------------- | ------------------------------ | --------------------------------- |
1593-
| Read (p50) | <0.5ms | Single key, no proof, follower | redb lookup + gRPC |
1593+
| Read (p50) | <0.5ms | Single key, no proof, follower | inkwell lookup + gRPC |
15941594
| Read (p99) | <2ms | Single key, no proof, follower | Tail latency from GC/compaction |
15951595
| Read + proof (p99) | <10ms | With merkle proof generation | Bucket-based O(k) proof |
15961596
| Write (p50) | <10ms | Single tx, quorum commit | Raft RTT + fsync |
@@ -1600,7 +1600,7 @@ Scaling dimensions:
16001600

16011601
**Why these targets are achievable:**
16021602

1603-
- **Read p99 <2ms**: Follower reads bypass Raft consensus. redb B-tree lookup is O(log n). etcd achieves ~2ms p99 for serializable reads.
1603+
- **Read p99 <2ms**: Follower reads bypass Raft consensus. inkwell B+ tree lookup is O(log n). etcd achieves ~2ms p99 for serializable reads.
16041604
- **Write p99 <50ms**: Aggressive for blockchain but achievable because:
16051605
- Bucket-based state root: O(k) where k = dirty keys, not O(n) full tree
16061606
- Single Raft RTT: ~1-2ms same datacenter
@@ -2583,17 +2583,17 @@ Each node uses a single data directory with subdirectories per concern:
25832583
├── shards/
25842584
│ ├── _system/ # System Raft group
25852585
│ │ ├── raft/ # Openraft storage
2586-
│ │ │ ├── log.redb # Raft log entries
2586+
│ │ │ ├── log.inkwell # Raft log entries
25872587
│ │ │ └── vote # Current term + voted_for
2588-
│ │ ├── state.redb # State machine (system registry)
2588+
│ │ ├── state.inkwell # State machine (system registry)
25892589
│ │ └── snapshots/
25902590
│ │ ├── 000001000.snap # Snapshot at height 1000
25912591
│ │ └── 000002000.snap # Snapshot at height 2000
25922592
│ ├── shard_0001/ # User shard group
25932593
│ │ ├── raft/
2594-
│ │ │ ├── log.redb
2594+
│ │ │ ├── log.inkwell
25952595
│ │ │ └── vote
2596-
│ │ ├── state.redb # State for all vaults in shard
2596+
│ │ ├── state.inkwell # State for all vaults in shard
25972597
│ │ ├── blocks/
25982598
│ │ │ ├── segment_000000.blk # Blocks 0-9999
25992599
│ │ │ ├── segment_000001.blk # Blocks 10000-19999
@@ -2610,26 +2610,26 @@ Each node uses a single data directory with subdirectories per concern:
26102610
| Decision | Rationale |
26112611
| ----------------------------- | ---------------------------------------------------------- |
26122612
| Per-shard directories | Matches Raft group boundaries; independent failure domains |
2613-
| Separate raft/ and state.redb | Raft log is append-heavy; state is random-access heavy |
2613+
| Separate raft/ and state.inkwell | Raft log is append-heavy; state is random-access heavy |
26142614
| Block segments | Append-only writes; easy archival of old segments |
26152615
| Snapshots by height | Predictable naming; simple retention policy |
26162616

2617-
### Storage Backend: redb
2617+
### Storage Backend: inkwell
26182618

2619-
[redb](https://github.com/cberner/redb) provides ACID transactions with a simple API. Each shard uses two redb databases:
2619+
inkwell is our custom B+ tree storage engine providing ACID transactions with MVCC. Each shard uses two inkwell databases:
26202620

2621-
**raft/log.redb** — Raft log storage:
2621+
**raft/log.inkwell** — Raft log storage:
26222622

26232623
```rust
2624-
// Tables in log.redb
2624+
// Tables in log.inkwell
26252625
const LOG_ENTRIES: TableDefinition<u64, &[u8]> = TableDefinition::new("log");
26262626
const LOG_META: TableDefinition<&str, &[u8]> = TableDefinition::new("meta");
26272627

2628-
struct RedbLogStorage {
2628+
struct InkwellLogStorage {
26292629
db: Database,
26302630
}
26312631

2632-
impl RaftLogStorage<TypeConfig> for RedbLogStorage {
2632+
impl RaftLogStorage<TypeConfig> for InkwellLogStorage {
26332633
async fn get_log_state(&mut self) -> Result<LogState<TypeConfig>> {
26342634
let txn = self.db.begin_read()?;
26352635
let table = txn.open_table(LOG_META)?;
@@ -2712,10 +2712,10 @@ impl RaftLogStorage<TypeConfig> for RedbLogStorage {
27122712
}
27132713
```
27142714

2715-
**state.redb** — State machine storage:
2715+
**state.inkwell** — State machine storage:
27162716

27172717
```rust
2718-
// Tables in state.redb (per shard, contains all vaults)
2718+
// B+ tree tables in state.inkwell (per shard, contains all vaults)
27192719
const RELATIONSHIPS: TableDefinition<&[u8], &[u8]> = TableDefinition::new("rel");
27202720
const ENTITIES: TableDefinition<&[u8], &[u8]> = TableDefinition::new("ent");
27212721
const OBJ_INDEX: TableDefinition<&[u8], &[u8]> = TableDefinition::new("obj_idx");
@@ -2730,7 +2730,7 @@ fn make_key(vault_id: VaultId, key: &[u8]) -> Vec<u8> {
27302730
}
27312731
```
27322732

2733-
**Per-shard isolation**: All vaults in a shard share a single redb instance. Keys are prefixed with `vault_id` for isolation. This reduces file handle count and enables cross-vault operations within a shard if needed.
2733+
**Per-shard isolation**: All vaults in a shard share a single inkwell database. Keys are prefixed with `vault_id` for isolation. This reduces file handle count and enables cross-vault operations within a shard if needed.
27342734

27352735
### Block Archive Format
27362736

@@ -2886,7 +2886,7 @@ impl Node {
28862886
let shard_id = parse_shard_id(&shard_dir.file_name())?;
28872887

28882888
// 3. Recover Raft state
2889-
let raft_storage = RedbLogStorage::open(shard_dir.join("raft/log.redb"))?;
2889+
let raft_storage = RedbLogStorage::open(shard_dir.join("raft/log.inkwell"))?;
28902890
let vote = raft_storage.read_vote().await?;
28912891
let log_state = raft_storage.get_log_state().await?;
28922892

@@ -2936,7 +2936,7 @@ impl Node {
29362936
| Failure Mode | Recovery Action |
29372937
| -------------------- | --------------------------------------------- |
29382938
| Clean shutdown | Replay from last snapshot + committed log |
2939-
| Crash during write | Incomplete redb txn rolled back automatically |
2939+
| Crash during write | Incomplete inkwell txn rolled back automatically |
29402940
| Corrupted snapshot | Skip to older snapshot, replay more log |
29412941
| Corrupted log entry | Fetch from peer, or rebuild from snapshot |
29422942
| Missing segment file | Fetch from peer (block archive is replicated) |
@@ -2962,7 +2962,7 @@ impl Node {
29622962
### Storage Invariants
29632963

29642964
30. **Raft log durability**: Log entries are fsync'd before Raft acknowledgment
2965-
31. **State consistency**: `state.redb` reflects all applied log entries up to `applied_index`
2965+
31. **State consistency**: `state.inkwell` reflects all applied log entries up to `applied_index`
29662966
32. **Block archive append-only**: Segment files are never modified after creation (only new segments appended)
29672967
33. **Snapshot validity**: Snapshot `state_root` matches block header at `shard_height`
29682968

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ Ledger is InferaDB's persistence layer — a blockchain database for authorizati
6060
│ ├─ Vault: prod [chain] │ ├─ Vault: main [chain] │
6161
│ └─ Vault: staging [chain] │ └─ ... │
6262
├─────────────────────────────────────────────────────────────┤
63-
State Layer (redb)
63+
│ State Layer (inkwell)
6464
│ Relationships │ Entities │ Indexes │ State Roots │
6565
└─────────────────────────────────────────────────────────────┘
6666
```

crates/inkwell/Cargo.toml

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
[package]
2+
name = "inkwell"
3+
version.workspace = true
4+
edition = "2021"
5+
description = "Purpose-built embedded storage engine for InferaDB Ledger"
6+
license.workspace = true
7+
repository.workspace = true
8+
authors.workspace = true
9+
10+
[dependencies]
11+
# Hashing for checksums
12+
xxhash-rust = { workspace = true }
13+
14+
# Error handling
15+
thiserror = { workspace = true }
16+
17+
# Serialization for key/value encoding
18+
byteorder = { workspace = true }
19+
20+
# Memory-mapped files
21+
memmap2 = { workspace = true }
22+
23+
# Parking lot for faster locks
24+
parking_lot = { workspace = true }
25+
26+
# Logging
27+
tracing = { workspace = true }
28+
29+
[dev-dependencies]
30+
tempfile = { workspace = true }
31+
proptest = { workspace = true }
32+
rand = { workspace = true }
33+
34+
[features]
35+
default = []
36+
# Enable additional safety checks (slower but catches bugs)
37+
paranoid = []

0 commit comments

Comments
 (0)