# TYP-006: ChainTip 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.4 |

---

## Summary

ChainTip tracks the current canonical chain tip (hash + height) with a fixed 40-byte binary encoding for efficient persistence in CF_METADATA.

---

## Specification

```rust
use chia_protocol::Bytes32;

/// The current canonical chain tip, persisted under META_TIP in CF_METADATA.
///
/// Binary encoding is exactly 40 bytes:
///   bytes[0..32]  = hash (Bytes32, big-endian as stored)
///   bytes[32..40] = height (u64, little-endian)
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ChainTip {
    /// Hash of the tip block.
    pub hash: Bytes32,
    /// Height of the tip block.
    pub height: u64,
}

impl ChainTip {
    /// Encode to a 40-byte array: hash(32) || height(8 LE).
    pub fn to_bytes(&self) -> [u8; 40] {
        let mut buf = [0u8; 40];
        buf[0..32].copy_from_slice(self.hash.as_ref());
        buf[32..40].copy_from_slice(&self.height.to_le_bytes());
        buf
    }

    /// Decode from a 40-byte slice.
    ///
    /// Returns an error if the slice length is not exactly 40 bytes.
    pub fn from_bytes(bytes: &[u8]) -> Result<Self, BlockStoreError> {
        if bytes.len() != 40 {
            return Err(BlockStoreError::Serialization(format!(
                "ChainTip requires 40 bytes, got {}",
                bytes.len()
            )));
        }
        let mut hash_bytes = [0u8; 32];
        hash_bytes.copy_from_slice(&bytes[0..32]);
        let hash = Bytes32::new(hash_bytes);

        let mut height_bytes = [0u8; 8];
        height_bytes.copy_from_slice(&bytes[32..40]);
        let height = u64::from_le_bytes(height_bytes);

        Ok(Self { hash, height })
    }
}
```

### Encoding Layout

```
Offset  Size  Field       Encoding
------  ----  ----------  --------
0       32    hash        Raw Bytes32 (as-is)
32       8    height      u64 little-endian
------  ----
Total:  40 bytes
```

### Design Decisions

- **Little-endian height:** Matches Rust's native byte order for u64, avoiding unnecessary conversion.
- **Fixed 40-byte size:** Enables O(1) parsing without length prefixes or delimiters.
- **Copy semantics:** ChainTip is small (40 bytes) and derives `Copy` for efficient passing.

---

## Acceptance Criteria

- [x] `ChainTip` has `hash: Bytes32` and `height: u64` fields
- [x] `ChainTip` derives `Debug`, `Clone`, `Copy`, `PartialEq`, `Eq`
- [x] `to_bytes()` returns exactly 40 bytes
- [x] `to_bytes()` encodes hash in bytes[0..32] and height LE in bytes[32..40]
- [x] `from_bytes()` accepts exactly 40 bytes and returns Ok
- [x] `from_bytes()` returns Err for any other length
- [x] Round-trip: `from_bytes(&tip.to_bytes()) == Ok(tip)` for any valid tip
- [x] Known value test: specific hash + height produces expected byte output

---

## Implementation Notes

- Define in `src/types/chain_tip.rs`.
- `Bytes32` from chia-protocol implements `AsRef<[u8]>`; construction from `[u8; 32]` uses `Bytes32::new` in this crate.
- **ERR mapping:** [`ERR-001`](../../error_types/specs/ERR-001_blockstore_error_enum.md) does not include `InvalidData`. Wrong-length tip payloads use `BlockStoreError::Serialization` with a stable message prefix (`ChainTip requires 40 bytes`), consistent with other malformed fixed-width metadata values.
- This encoding is used when reading/writing the `META_TIP` key in CF_METADATA.

---

## Test Plan

| Test Name | Type | Description |
|-----------|------|-------------|
| `test_to_bytes_length` | unit | Verify `to_bytes()` returns exactly 40 bytes |
| `test_to_bytes_hash_position` | unit | Verify bytes[0..32] matches hash |
| `test_to_bytes_height_position` | unit | Verify bytes[32..40] matches height in LE |
| `test_from_bytes_roundtrip` | unit | `from_bytes(&to_bytes())` produces original ChainTip |
| `test_from_bytes_wrong_length` | unit | Pass 39 bytes, verify Err returned |
| `test_from_bytes_empty` | unit | Pass 0 bytes, verify Err returned |
| `test_from_bytes_too_long` | unit | Pass 41 bytes, verify Err returned |
| `test_known_encoding` | unit | Hash=all 0xFF, height=42 -> verify exact byte output |
| `test_height_zero` | unit | Roundtrip with height=0 |
| `test_height_max` | unit | Roundtrip with height=u64::MAX |
| `test_copy_semantics` | unit | Assign to new variable, verify original unchanged |

---

## Expected Test Files

- `tests/typ_006_tests.rs`
