# STR-004: BlockStore Constructor

| Link | Path |
|------|------|
| **Normative** | [NORMATIVE.md](../NORMATIVE.md) |
| **Verification** | [VERIFICATION.md](../VERIFICATION.md) |
| **Tracking** | [TRACKING.yaml](../TRACKING.yaml) |
| **Spec** | [SPEC.md](../../../../resources/SPEC.md) Section 15.1 |

---

## Summary

BlockStore MUST provide `open()`, `open_readonly()`, and `init_genesis()` methods for creating, opening, and initializing the block store.

---

## Specification

### `open(config: BlockStoreConfig) -> Result<Self>`

Opens or creates a RocksDB-backed block store at the configured path.

```rust
impl BlockStore {
    pub fn open(config: BlockStoreConfig) -> Result<Self, BlockStoreError> {
        // 1. Configure RocksDB options from config
        // 2. Create/open DB with all column families:
        //    CF_BLOCKS, CF_HEADERS, CF_ATTESTED,
        //    CF_CANONICAL, CF_CHECKPOINTS, CF_METADATA
        // 3. Load current tip from CF_METADATA (if exists)
        // 4. If config.warm_cache_on_open, warm caches
        // 5. Return initialized BlockStore
    }
}
```

Behavior:
- If the database directory does not exist, it MUST be created.
- All six column families MUST be created if they do not already exist.
- If a tip exists in `CF_METADATA`, it MUST be loaded into the in-memory `ChainTip`.
- If `warm_cache_on_open` is true, recent blocks and headers MUST be loaded into cache.

### `open_readonly(path: impl AsRef<Path>) -> Result<Self>`

Opens an existing store in read-only mode.

```rust
impl BlockStore {
    pub fn open_readonly(path: impl AsRef<Path>) -> Result<Self, BlockStoreError> {
        // 1. Open DB in read-only mode
        // 2. Load tip from CF_METADATA
        // 3. Return BlockStore (writes will error)
    }
}
```

Behavior:
- MUST NOT create the database if it does not exist (return error).
- All write operations MUST return an error when called on a read-only store.

### `init_genesis(block: &L2Block) -> Result<()>`

Initializes the store with a genesis block.

```rust
impl BlockStore {
    pub fn init_genesis(&self, block: &L2Block) -> Result<(), BlockStoreError> {
        // 1. Verify store is empty (no existing tip)
        // 2. Store block in CF_BLOCKS
        // 3. Store header in CF_HEADERS
        // 4. Set canonical index at height 0
        // 5. Set tip to ChainTip { hash: block.hash(), height: 0 }
        // 6. Store genesis hash in CF_METADATA
    }
}
```

Behavior:
- MUST fail if a tip already exists (store already initialized).
- MUST atomically write block, header, canonical entry, and tip in a single WriteBatch.
- After success, `tip()` MUST return `Some(ChainTip { hash, height: 0 })`.

---

## Acceptance Criteria

- [x] `open()` creates a new database when path does not exist
- [x] `open()` opens an existing database without data loss
- [x] `open()` creates all six column families
- [x] `open()` loads existing tip from metadata
- [x] `open()` warms cache when `warm_cache_on_open` is true
- [x] `open_readonly()` succeeds on an existing database
- [x] `open_readonly()` fails when path does not exist
- [x] `open_readonly()` prevents write operations
- [x] `init_genesis()` stores genesis block and header
- [x] `init_genesis()` sets tip to height 0
- [x] `init_genesis()` records genesis hash in metadata
- [x] `init_genesis()` fails if store is already initialized
- [x] `init_genesis()` writes are atomic (WriteBatch)

---

## Implementation Notes

- Use `rocksdb::DB::open_cf()` with a list of column family descriptors.
- Use `rocksdb::DB::open_cf_for_read_only()` for the read-only path.
- The genesis block hash should be computed via `chia-sha2` hashing of the serialized header.
- Cache warming should load the most recent N blocks/headers where N is the cache capacity.

---

## Test Plan

| Test Name | Type | Description |
|-----------|------|-------------|
| `test_open_creates_new_db` | unit | Open with temp dir, verify DB files created |
| `test_open_reopens_existing_db` | unit | Open, write data, close, reopen, verify data persists |
| `test_open_creates_all_cfs` | unit | Open and list column families, verify all six present |
| `test_open_loads_existing_tip` | unit | Init genesis, close, reopen, verify tip is loaded |
| `test_open_warms_cache` | unit | Init genesis + blocks, close, reopen with warm=true, verify cache populated |
| `test_open_readonly_existing` | unit | Create store, open readonly, verify reads succeed |
| `test_open_readonly_missing_fails` | unit | Open readonly on nonexistent path, verify error |
| `test_open_readonly_prevents_writes` | unit | Open readonly, attempt write, verify error |
| `test_init_genesis_stores_block` | unit | Init genesis, retrieve block by hash, verify match |
| `test_init_genesis_sets_tip` | unit | Init genesis, verify tip height=0 and correct hash |
| `test_init_genesis_records_hash` | unit | Init genesis, read META_GENESIS_HASH, verify match |
| `test_init_genesis_fails_if_initialized` | unit | Init genesis twice, verify second call returns error |

---

## Expected Test Files

- `tests/str_004_tests.rs`
