1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
//! Storage backend abstraction for dig-coinstore.
//!
//! Defines the [`StorageBackend`] trait that abstracts over LMDB and RocksDB.
//! Both backends implement this trait, and the rest of the crate interacts
//! with storage exclusively through this interface.
//!
//! Backend selection is compile-time via Cargo feature gates:
//! - `rocksdb-storage` → [`rocksdb`] module
//! - `lmdb-storage` → [`lmdb`] module
//!
//! # Design rationale
//!
//! The trait uses `&str` for column family names (matching the string constants
//! in [`schema`]) and `&[u8]` / `Vec<u8>` for keys and values. This avoids
//! generic type parameters that would complicate dynamic dispatch while keeping
//! the API flexible enough for any key encoding scheme.
//!
//! # Requirements: STR-003, STO-001
//! # Spec: docs/requirements/domains/storage/specs/STO-001.md
//! # SPEC.md: Section 7 (Storage Architecture)
//!
//! **Verification:** behavioral acceptance for [`StorageBackend`] lives in [`tests/sto_001_tests.rs`](../../tests/sto_001_tests.rs)
//! (RocksDB by default; run with `--no-default-features --features lmdb-storage` for the LMDB slice).
//! **STO-003 (six LMDB databases, MVCC, `MapFull`):** [`tests/sto_003_tests.rs`](../../tests/sto_003_tests.rs).
//! **STO-002 (Rocks column families):** [`tests/sto_002_tests.rs`](../../tests/sto_002_tests.rs).
//! **STO-004 (RocksDB bloom / prefix / L0 pin):** [`tests/sto_004_tests.rs`](../../tests/sto_004_tests.rs).
//! **STO-005 (WriteBatch atomic + durable commit):** [`tests/sto_005_tests.rs`](../../tests/sto_005_tests.rs).
//! **STO-006 (RocksDB compaction + per-CF memtable depth):** [`tests/sto_006_tests.rs`](../../tests/sto_006_tests.rs).
//! **STO-007 (feature gates + factory):** [`tests/sto_007_tests.rs`](../../tests/sto_007_tests.rs); compile-time guard
//! in [`crate`](../../src/lib.rs) (`compile_error!` when no backend feature).
//! **STO-008 (bincode + key helpers):** [`kv_bincode`](kv_bincode), [`schema`](schema); tests in
//! [`tests/sto_008_tests.rs`](../../tests/sto_008_tests.rs).
// ─────────────────────────────────────────────────────────────────────────────
// STO-007: open concrete backend from configuration
// ─────────────────────────────────────────────────────────────────────────────
/// Open the key-value backend selected by [`crate::config::StorageBackend`] using a full [`crate::config::CoinStoreConfig`].
///
/// **Why this exists:** [`crate::coin_store::CoinStore`] needs the same match logic; centralizing it here avoids
/// drift between the public factory and internal construction, and gives embedders a small API when they only
/// want `Box<dyn StorageBackend>` without spinning up [`crate::coin_store::CoinStore`].
///
/// **Compile-time (STO-007):** [`crate`] refuses to build unless at least one of `rocksdb-storage` or `lmdb-storage`
/// is enabled. If the **requested** engine was compiled out (e.g. config says RocksDb but crate was built with
/// `--no-default-features --features lmdb-storage` only), this returns [`StorageError::BackendError`] with a stable
/// message rather than linking the wrong native library.
///
/// # Errors
///
/// Propagates [`StorageError`] from the selected backend's `open` implementation, or
/// [`StorageError::BackendError`] when the engine does not match enabled features.
///
/// # Requirement: STO-007
/// # Spec: docs/requirements/domains/storage/specs/STO-007.md
// ─────────────────────────────────────────────────────────────────────────────
// StorageError
// ─────────────────────────────────────────────────────────────────────────────
/// Errors from storage backend operations.
///
/// This is a low-level error type used by [`StorageBackend`] implementations.
/// Higher-level code wraps this into `CoinStoreError::StorageError`.
/// A key-value pair returned from prefix scans.
pub type KvPair = ;
// ─────────────────────────────────────────────────────────────────────────────
// WriteBatch
// ─────────────────────────────────────────────────────────────────────────────
/// A single operation within a [`WriteBatch`].
///
/// Operations are accumulated in memory and then committed atomically
/// via [`StorageBackend::batch_write`].
/// An atomic batch of write operations.
///
/// Accumulates [`WriteOp`]s in memory, then commits them all at once
/// via [`StorageBackend::batch_write`]. This ensures either all writes
/// succeed or none do (atomicity). On RocksDB, the backend maps this to a
/// single native write batch with **durable** WAL options (`sync=true`; see
/// `src/storage/rocksdb.rs`); on LMDB, one write transaction + commit.
///
/// # Usage
///
/// ```ignore
/// let mut batch = WriteBatch::new();
/// batch.put("coin_records", &coin_key, &serialized_record);
/// batch.put("coin_by_puzzle_hash", &ph_key, &coin_id);
/// batch.delete("unspent_by_puzzle_hash", &old_ph_key);
/// backend.batch_write(batch)?;
/// ```
///
/// # Requirement: STO-005
/// # Spec: docs/requirements/domains/storage/specs/STO-005.md
// ─────────────────────────────────────────────────────────────────────────────
// StorageBackend trait
// ─────────────────────────────────────────────────────────────────────────────
/// Trait abstracting over key-value storage backends (RocksDB, LMDB).
///
/// All storage access in dig-coinstore goes through this trait. [`crate::coin_store::CoinStore`]
/// holds `Box<dyn StorageBackend>` (aliased internally as `KvStore`) so block application, queries,
/// and snapshots stay engine-agnostic ([`STO-001.md`](../../docs/requirements/domains/storage/specs/STO-001.md)).
///
/// # Method semantics (normative summary)
///
/// | Method | Contract |
/// |--------|----------|
/// | [`Self::get`] | `Ok(None)` if key missing — **not** an error ([`STO-001`](../../docs/requirements/domains/storage/specs/STO-001.md) § Method Semantics). |
/// | [`Self::put`] | Upsert; replaces existing value. |
/// | [`Self::delete`] | **Idempotent:** missing key is `Ok(())` (both engines normalize this). |
/// | [`Self::batch_write`] | All-or-nothing; empty batch is a no-op ([`STO-005`](../../docs/requirements/domains/storage/specs/STO-005.md) for WAL/fsync depth). |
/// | [`Self::prefix_scan`] | Keys with prefix `prefix`, ordered lexicographically by key (iterator contract). |
/// | [`Self::flush`] | Best-effort durability hint (Rocks WAL flush; LMDB `force_sync`). |
/// | [`Self::compact`] | Engine-specific maintenance (Rocks manual compaction; LMDB currently no-op). |
///
/// # Thread safety
///
/// Implementations MUST be `Send + Sync` so callers can share `Arc<dyn StorageBackend>` across
/// threads and satisfy [`CON-001`](../../docs/requirements/domains/concurrency/specs/CON-001.md).
///
/// # Column families
///
/// The `cf` parameter names logical stores from [`schema`] (same string table for Rocks column
/// families and LMDB named databases). Unknown `cf` → [`StorageError::UnknownColumnFamily`].
///
/// # Verification
///
/// Behavioral contract: [`tests/sto_001_tests.rs`](../../tests/sto_001_tests.rs) (per-domain file for STO-001).
/// Compile-time surface: [`tests/str_003_tests.rs`](../../tests/str_003_tests.rs) (STR-003 overlap).
///
/// # Requirement: STO-001
/// # Spec: docs/requirements/domains/storage/specs/STO-001.md