Skip to content

Commit c906170

Browse files
CopilotSteake
andcommitted
Address code review feedback: add validation, edge cases, and docs
Co-authored-by: Steake <530040+Steake@users.noreply.github.com>
1 parent 24c61df commit c906170

3 files changed

Lines changed: 55 additions & 5 deletions

File tree

crates/bitcell-state/src/storage.rs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,12 @@ impl StorageManager {
337337
// Store snapshot data with metadata: height(8) | root_len(4) | state_root | accounts_data
338338
let mut snapshot_data = Vec::new();
339339
snapshot_data.extend_from_slice(&height.to_be_bytes());
340+
341+
// Validate state_root length to prevent integer overflow
342+
if state_root.len() > u32::MAX as usize {
343+
return Err("State root too large (exceeds u32::MAX)".to_string());
344+
}
345+
340346
snapshot_data.extend_from_slice(&(state_root.len() as u32).to_be_bytes());
341347
snapshot_data.extend_from_slice(state_root);
342348
snapshot_data.extend_from_slice(accounts_data);
@@ -390,6 +396,14 @@ impl StorageManager {
390396
.map_err(|_| "Invalid snapshot height in data".to_string())?
391397
);
392398

399+
// Validate stored height matches expected height from index
400+
if stored_height != height {
401+
return Err(format!(
402+
"Snapshot height mismatch: index says {}, data says {}",
403+
height, stored_height
404+
));
405+
}
406+
393407
let root_len = u32::from_be_bytes(
394408
snapshot_data[8..12].try_into()
395409
.map_err(|_| "Invalid root length in data".to_string())?
@@ -434,6 +448,14 @@ impl StorageManager {
434448
.map_err(|_| "Invalid snapshot height in data".to_string())?
435449
);
436450

451+
// Validate stored height matches requested height
452+
if stored_height != height {
453+
return Err(format!(
454+
"Snapshot height mismatch: expected {}, got {}",
455+
height, stored_height
456+
));
457+
}
458+
437459
let root_len = u32::from_be_bytes(
438460
snapshot_data[8..12].try_into()
439461
.map_err(|_| "Invalid root length in data".to_string())?
@@ -811,6 +833,33 @@ mod tests {
811833
assert_eq!(snap2.2.as_slice(), b"data2");
812834
}
813835

836+
#[test]
837+
fn test_snapshot_edge_cases() {
838+
let temp_dir = TempDir::new().unwrap();
839+
let storage = StorageManager::new(temp_dir.path()).unwrap();
840+
841+
// Test empty state_root
842+
storage.create_snapshot(100, &[], b"data").unwrap();
843+
let snap = storage.get_snapshot(100).unwrap().unwrap();
844+
assert_eq!(snap.0, 100);
845+
assert_eq!(snap.1.len(), 0);
846+
assert_eq!(snap.2.as_slice(), b"data");
847+
848+
// Test empty accounts_data
849+
storage.create_snapshot(101, b"root", &[]).unwrap();
850+
let snap = storage.get_snapshot(101).unwrap().unwrap();
851+
assert_eq!(snap.0, 101);
852+
assert_eq!(snap.1.as_slice(), b"root");
853+
assert_eq!(snap.2.len(), 0);
854+
855+
// Test both empty
856+
storage.create_snapshot(102, &[], &[]).unwrap();
857+
let snap = storage.get_snapshot(102).unwrap().unwrap();
858+
assert_eq!(snap.0, 102);
859+
assert_eq!(snap.1.len(), 0);
860+
assert_eq!(snap.2.len(), 0);
861+
}
862+
814863
#[test]
815864
fn test_account_persistence() {
816865
let temp_dir = TempDir::new().unwrap();

crates/bitcell-state/tests/storage_persistence_test.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,11 @@ fn test_multi_block_persistence() {
5858
assert!(header.is_some(), "Block {} not found", height);
5959
}
6060

61-
// Verify transactions
62-
for height in 0..100 {
63-
let sender = format!("sender_{:033}", height % 10);
61+
// Verify transactions - check each unique sender once
62+
for sender_id in 0..10 {
63+
let sender = format!("sender_{:033}", sender_id);
6464
let txs = storage.get_transactions_by_sender(sender.as_bytes(), 0).unwrap();
65-
assert!(txs.len() >= 10, "Expected at least 10 transactions for sender at height {}", height);
65+
assert_eq!(txs.len(), 100, "Expected 100 transactions for sender {}", sender_id);
6666
}
6767

6868
// Verify state roots

docs/STORAGE.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ The storage layer uses separate RocksDB column families for different data types
3030

3131
1. **Multiple Indexes**: Blocks and headers are indexed by both height and hash for O(1) lookups
3232
2. **Sender Index**: Transactions use a composite key (sender||height||tx_hash) for efficient range queries
33-
3. **Atomic Writes**: All multi-key operations use `WriteBatch` for atomicity
33+
3. **Atomic Writes**: Multi-key operations use `WriteBatch` for atomicity (blocks, headers, state roots, snapshots)
34+
> **Note:** Transaction deletion in production pruning is not fully implemented yet. Transaction storage uses atomic batches.
3435
4. **Snapshots**: Variable-length snapshot format with length prefix for flexibility
3536
5. **Separation of Concerns**: Block data, state data, and indexes are in separate column families
3637

0 commit comments

Comments
 (0)