# CAN-005: extend_chain

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

---

## Summary

`extend_chain(&self, block: &L2Block) -> Result<bool>` MUST store the block, set it as canonical, and update the chain tip atomically. Returns `false` if the block is a duplicate. This is the primary block ingestion API for normal chain-following operation.

---

## Specification

### Function Signature

```rust
pub fn extend_chain(&self, block: &L2Block) -> Result<bool>
```

### Execution Steps

1. **Duplicate check**: If the block hash is already in the store, return `Ok(false)` immediately.

2. **Store block**: Call `self.put(block)?` which writes the block body to `CF_BLOCKS`, the header to `CF_HEADERS`, and creates a `BlockRecord` (see BLK domain).

3. **Set canonical**: Call `self.set_canonical(&block.hash())?` to write the height-to-hash mapping to both CF_CANONICAL and the mmap file.

4. **Update tip**: Call `self.set_tip(ChainTip { hash: block.hash(), height: block.header.height })?` to persist the new chain tip in CF_METADATA.

5. **Return success**: Return `Ok(true)`.

```rust
pub fn extend_chain(&self, block: &L2Block) -> Result<bool> {
    let hash = block.hash();

    // Duplicate detection
    if self.has_block(&hash)? {
        return Ok(false);
    }

    // Store block data
    self.put(block)?;

    // Mark as canonical
    self.set_canonical(&hash)?;

    // Advance chain tip
    self.set_tip(ChainTip {
        hash,
        height: block.header.height,
    })?;

    Ok(true)
}
```

### Atomicity Considerations

While the individual operations (put, set_canonical, set_tip) are not wrapped in a single RocksDB transaction, the ordering ensures safe recovery:

1. If crash after `put` but before `set_canonical`: block is stored but not canonical. On restart, the tip is still at the previous block. The block can be re-ingested.
2. If crash after `set_canonical` but before `set_tip`: block is canonical but tip is stale. On restart, the tip can be corrected by scanning CF_CANONICAL for the highest entry.

---

## Acceptance Criteria

- [ ] Calling extend_chain with a new block returns `Ok(true)`
- [ ] After extend_chain, the block is retrievable via get_block
- [ ] After extend_chain, the block is canonical (get_hash_by_height returns its hash)
- [ ] After extend_chain, tip() returns the new block's hash and height
- [ ] Calling extend_chain with a duplicate block returns `Ok(false)`
- [ ] Duplicate call does not modify the chain tip or canonical index
- [ ] extend_chain with a chain of blocks (parent linking) builds a correct canonical chain

---

## Implementation Notes

- This method is the "happy path" for block ingestion during normal chain sync. Reorg cases use `apply_reorg` (ROR-003) instead.
- The caller is responsible for ensuring parent-child linking is correct (i.e., the block's parent_hash matches the current tip). This method does not validate chain continuity.
- For bulk sync, consider `extend_chain` in a loop vs. a dedicated bulk API using WriteBatch.

---

## Test Plan

| Test Name | Type | Description |
|-----------|------|-------------|
| `test_extend_chain_new_block` | unit | Create a block, call extend_chain, verify stored + canonical + tip updated |
| `test_extend_chain_duplicate` | unit | Call extend_chain twice with same block, verify second returns false |
| `test_extend_chain_builds_chain` | unit | Extend chain with 10 linked blocks, verify canonical chain and tip at height 9 |
| `test_extend_chain_tip_progression` | unit | After each extend_chain call, verify tip() reflects the latest block |
| `test_extend_chain_genesis` | unit | Use extend_chain for the first block (height 0), verify it becomes both genesis and tip |

---

## Expected Test Files

- `tests/can_005_tests.rs`
