# VAL-004: Reject Missing Coins

> **Authoritative requirement:** [VAL-004](../NORMATIVE.md#VAL-004)
> **Verification:** [VERIFICATION.md](../VERIFICATION.md)
> **Tracking:** [TRACKING.yaml](../TRACKING.yaml)
> **Spec reference:** [SPEC.md](../../../../resources/SPEC.md) — Section 5.1, 6.1

---

## Summary

Every coin referenced in a spend bundle must actually exist in the blockchain state. `validate_spend_bundle()` receives the relevant coin records through `context.coin_records` (a `HashMap<Bytes32, CoinRecord>`) and must verify that every coin being spent is present in this map. The one exception is ephemeral coins: coins created by earlier bundles in the same block that exist in `context.ephemeral_coins` but have not yet been committed to the persistent UTXO set.

On Chia L1, coin existence is checked during mempool admission in [`add_spend_bundle()`](https://github.com/Chia-Network/chia-blockchain/blob/main/chia/full_node/mempool_manager.py#L480) where the mempool looks up each spent coin in the coin store, and during block validation in [`validate_block_body()`](https://github.com/Chia-Network/chia-blockchain/blob/main/chia/consensus/block_body_validation.py#L191). On L1, ephemeral handling is done through `ASSERT_EPHEMERAL` conditions and the block body validator's tracking of intra-block coin creation.

This check prevents spending coins that were never created, coins that belong to a different chain, or coins whose IDs were computed incorrectly by the transaction creator. It is a fundamental safety check that prevents phantom value from entering the system.

---

## Specification

### Algorithm

For each `CoinSpend` in `bundle.coin_spends`:

1. Compute `coin_id = coin_spend.coin.coin_id()`.
2. Check if `coin_id` exists in `context.coin_records`.
3. If not found, check if `coin_id` exists in `context.ephemeral_coins`.
4. If found in neither, return `Err(ValidationError::CoinNotFound(coin_id))`.

```rust
fn check_coin_exists(
    coin_id: Bytes32,
    context: &ValidationContext,
) -> Result<(), ValidationError> {
    if context.coin_records.contains_key(&coin_id) {
        return Ok(());
    }
    if context.ephemeral_coins.contains(&coin_id) {
        return Ok(());
    }
    Err(ValidationError::CoinNotFound(coin_id))
}
```

### Error Details

```rust
#[error("Coin not found: {0}")]
CoinNotFound(Bytes32),
```

The `Bytes32` payload is the coin ID that could not be found, enabling the caller to identify which coin is missing from the provided context.

### Ephemeral Coin Support

A coin is considered ephemeral if it was created by an earlier spend bundle in the same block. The caller is responsible for populating `context.ephemeral_coins` with the coin IDs of coins created by prior bundles. When a coin ID is found in `ephemeral_coins`, it is treated as a valid spend target even though no `CoinRecord` exists for it in `coin_records`.

See [VAL-010](VAL-010.md) for the full ephemeral coin specification.

### Ordering

This check is part of the structural checks (step 1 in the validation flow), executed after the duplicate spend check (VAL-003) and before the already-spent check (VAL-005).

---

## Acceptance Criteria

- [ ] A bundle referencing a coin not in `coin_records` and not in `ephemeral_coins` is rejected with `ValidationError::CoinNotFound`.
- [ ] The `CoinNotFound` error contains the coin ID of the missing coin.
- [ ] A bundle referencing a coin present in `coin_records` passes this check.
- [ ] A bundle referencing a coin present in `ephemeral_coins` (but not `coin_records`) passes this check.
- [ ] A bundle with multiple missing coins fails on the first missing coin encountered.
- [ ] The check occurs before CLVM execution (no cost consumed on missing-coin bundles).

---

## Implementation Notes

- **Primary file:** `src/consensus/validate.rs`
- **Position in flow:** Second structural check (after VAL-003 duplicate check).
- **`coin_records` contract:** The caller provides only the coins relevant to this bundle, not the full UTXO set (see VAL-015). If the caller fails to include a coin that the bundle spends, `CoinNotFound` will be raised correctly.
- **Ephemeral coins:** The `ephemeral_coins` set contains only coin IDs (`Bytes32`), not full `CoinRecord` entries. This is sufficient for the existence check. For ephemeral coins, the puzzle hash mismatch check (VAL-006) must obtain the puzzle hash from the `CoinSpend.coin` field since there is no `CoinRecord` to compare against.
- **Edge case:** A coin that exists in both `coin_records` and `ephemeral_coins` is valid (the `coin_records` entry takes precedence for subsequent checks like already-spent).
- **Edge case:** If the caller provides an empty `coin_records` and empty `ephemeral_coins`, every coin in the bundle will be rejected.

---

## Verification

### Test Plan

| Test | Type | Description | Expected Result |
|------|------|-------------|-----------------|
| Missing coin rejected | Simulator test | Build bundle spending a coin absent from `coin_records` and `ephemeral_coins` | `Err(ValidationError::CoinNotFound(coin_id))` |
| Present coin passes | Simulator test | Build bundle spending a coin present in `coin_records` | Passes this check |
| Ephemeral coin passes | Simulator test | Build bundle spending a coin listed in `ephemeral_coins` only | Passes this check |
| Error payload correct | Simulator test | Trigger `CoinNotFound`, inspect the `Bytes32` | Matches the `coin_id()` of the missing coin |
| Multiple missing coins | Simulator test | Build bundle with two coins, both missing | First missing coin triggers error |
| Empty context rejects all | Unit test | Empty `coin_records` and `ephemeral_coins`, any bundle | `CoinNotFound` for first coin |

---

## Source Citations

- **SPEC.md Section 5.1** (lines 459-476): `ValidationContext` definition with `coin_records` and `ephemeral_coins` fields.
- **SPEC.md Section 5.1** (lines 469-471): "Coins being spent in this bundle (coin_id -> CoinRecord). Only the coins relevant to this validation -- NOT the full UTXO set."
- **SPEC.md Section 6.1** (line 641): Step 1 structural checks: "Verify all spent coins exist in context.coin_records."
- **SPEC.md Section 5.1** (line 535): `ValidationError::CoinNotFound(Bytes32)` definition.
- **L1 reference — `validate_block_body()`:** [`block_body_validation.py:191`](https://github.com/Chia-Network/chia-blockchain/blob/main/chia/consensus/block_body_validation.py#L191)
- **L1 reference — `check_time_locks()`:** [`check_time_locks.py:15`](https://github.com/Chia-Network/chia-blockchain/blob/main/chia/consensus/check_time_locks.py#L15) — uses coin records for time lock validation.

---

## References

- [VAL-003](VAL-003.md) — Duplicate spend check (precedes this check)
- [VAL-005](VAL-005.md) — Already-spent check (follows this check)
- [VAL-006](VAL-006.md) — Puzzle hash mismatch (also uses coin_records)
- [VAL-010](VAL-010.md) — Ephemeral coin support (detailed ephemeral specification)
- [VAL-015](VAL-015.md) — coin_records is minimal (caller provides only relevant coins)
