# TYP-004: BlockRecord Struct

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

---

## Summary

BlockRecord is an in-memory summary of a block's metadata, derived from L2BlockHeader. It is used for caching and chain management but is NOT persisted to RocksDB.

---

## Specification

```rust
use chia_protocol::Bytes32;
use dig_block::{L2BlockHeader, BlockStatus};

/// In-memory block metadata record derived from an L2BlockHeader.
/// Not persisted to RocksDB directly; reconstructed from headers on cache miss.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BlockRecord {
    // Identity
    pub hash: Bytes32,
    pub height: u64,
    pub epoch: u64,
    pub parent_hash: Bytes32,

    // Chain position
    pub in_canonical_chain: bool,
    pub status: BlockStatus,

    // Statistics
    pub timestamp: u64,
    pub proposer_index: u32,
    pub spend_bundle_count: u32,
    pub total_cost: u64,
    pub total_fees: u64,
    pub additions_count: u32,
    pub removals_count: u32,
    pub block_size: u64,

    // L1 anchor
    pub l1_height: u32,
    pub l1_hash: Bytes32,

    // State
    pub state_root: Bytes32,
}

impl BlockRecord {
    /// Construct a BlockRecord from an L2BlockHeader and a BlockStatus.
    ///
    /// Extracts all relevant fields from the header. Fields not present
    /// in the header (e.g., block_size) are set to 0.
    pub fn from_header(header: &L2BlockHeader, status: BlockStatus) -> Self {
        Self {
            hash: header.hash(),
            height: header.height,
            epoch: header.epoch,
            parent_hash: header.parent_hash,
            in_canonical_chain: status.is_canonical(),
            status,
            timestamp: header.timestamp,
            proposer_index: header.proposer_index,
            spend_bundle_count: header.spend_bundle_count,
            total_cost: header.total_cost,
            total_fees: header.total_fees,
            additions_count: header.additions_count,
            removals_count: header.removals_count,
            block_size: 0, // TYP-004 policy: not sourced from header for this record
            l1_height: header.l1_height,
            l1_hash: header.l1_hash,
            state_root: header.state_root,
        }
    }
}
```

### Key Design Decisions

- **In-memory only:** BlockRecord is never serialized to RocksDB. It exists only in the block record cache (CAC-003) and is reconstructed from the header on cache miss. The Rust type does not derive `serde::Serialize` to discourage accidental persistence.
- **block_size is 0 from `from_header`:** Even though `L2BlockHeader` carries a `block_size` field on the wire, this record’s `block_size` stays **`0`** until a later API (e.g. full block ingest) sets it—matching the original “header-only” cache miss path.
- **`in_canonical_chain` and `BlockStatus`:** [`dig_block::BlockStatus`](https://docs.rs/dig-block) (ATT-003) has **no** `Canonical` variant. Use **`status.is_canonical()`** (`false` only for `Orphaned` / `Rejected`).

---

## Acceptance Criteria

- [x] `BlockRecord` has all 18 fields as specified
- [x] `BlockRecord` derives `Debug`, `Clone`, `PartialEq`, `Eq`
- [x] `from_header()` correctly maps all header fields to record fields
- [x] `from_header()` sets `hash` to the header's computed hash
- [x] `from_header()` sets `in_canonical_chain` from [`BlockStatus::is_canonical`](https://docs.rs/dig-block)
- [x] `from_header()` sets `block_size` to 0 (ignores header `block_size` for this record)
- [x] `BlockRecord` is NOT serialized to any column family (no `Serialize`; in-memory type only)
- [x] `BlockRecord` can be constructed and used without a RocksDB instance

---

## Implementation Notes

- Define in `src/types/block_record.rs`.
- The `hash()` method on `L2BlockHeader` comes from dig-block and uses chia-sha2 for hashing.
- `BlockStatus` is defined in dig-block (e.g., `Canonical`, `Pending`, `Orphan`).
- Future enhancement: add `with_block_size(size: u64)` builder method.

---

## Test Plan

| Test Name | Type | Description |
|-----------|------|-------------|
| `test_from_header_identity_fields` | integration | Verify hash, height, epoch, parent_hash are correctly extracted |
| `test_from_header_validated_is_canonical_chain` | integration | `Validated` → `in_canonical_chain == true` (`is_canonical`) |
| `test_from_header_orphaned_not_canonical_chain` | integration | `Orphaned` → `in_canonical_chain == false` |
| `test_from_header_rejected_not_canonical_chain` | integration | `Rejected` → `in_canonical_chain == false` |
| `test_from_header_pending_follows_is_canonical_predicate` | integration | Documents `Pending` + `is_canonical()` coupling |
| `test_from_header_statistics` | integration | Verify timestamp, proposer_index, spend_bundle_count, costs, fees, counts |
| `test_from_header_l1_anchor` | integration | Verify l1_height and l1_hash from header |
| `test_from_header_state_root` | integration | Verify state_root matches header |
| `test_from_header_block_size_zero` | integration | Header `block_size` may be non-zero; record still `0` |
| `test_block_record_clone_eq` | integration | Clone a BlockRecord, assert equality with original |
| `test_block_record_debug` | integration | Format with `{:?}`, verify non-empty |
| `test_block_record_constructed_without_rocksdb` | integration | No `BlockStore` / DB handle required |
| `test_block_record_field_count_matches_normative` | integration | Compile-time tuple destructure = 18 fields |

---

## Expected Test Files

- `tests/typ_004_tests.rs`
