# API-005: ValidationError Variants

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

---

## Summary

`ValidationError` is the crate's primary error type, returned by `validate_spend_bundle()`, `validate_block()`, and related functions. It MUST be an enum with exactly nine variants, each carrying enough context to identify the specific failure: which coin failed, what mismatch occurred, or what limit was exceeded. This enables callers (validators, mempools, debugging tools) to provide actionable error messages without re-inspecting the spend bundle.

The variants cover every category of validation failure that can occur during L2 spend bundle processing: CLVM execution errors (delegated to `chia-consensus`), coin state errors (not found, already spent, double-spent within a bundle), puzzle hash mismatches, BLS signature failures, conservation law violations (total output exceeds total input), CLVM cost overruns, and driver-layer errors from `chia-sdk-driver`. Each variant is designed to carry the minimum context needed for diagnosis.

The error design follows the Chia L1 pattern where [`ValidationErr`](https://github.com/Chia-Network/chia-blockchain/blob/main/chia/consensus/multiprocess_validation.py#L17) provides structured error information from the Rust layer. `ValidationError` wraps this upstream type for CLVM-level errors while adding L2-specific variants for coin state and conservation checks that the DIG orchestration layer performs.

---

## Specification

### Complete Enum Definition

```rust
#[derive(Debug, thiserror::Error)]
pub enum ValidationError {
    #[error("CLVM execution failed: {0}")]
    Clvm(chia_consensus::validation_error::ValidationErr),

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

    #[error("Coin already spent: {0}")]
    AlreadySpent(Bytes32),

    #[error("Double spend in bundle: {0}")]
    DoubleSpend(Bytes32),

    #[error("Puzzle hash mismatch: {0}")]
    PuzzleHashMismatch(Bytes32),

    #[error("Signature verification failed")]
    SignatureFailed,

    #[error("Conservation violation: input={input}, output={output}")]
    ConservationViolation { input: u64, output: u64 },

    #[error("Cost exceeded: limit={limit}, consumed={consumed}")]
    CostExceeded { limit: Cost, consumed: Cost },

    #[error("Driver error: {0}")]
    Driver(#[from] DriverError),
}
```

### Variant Semantics

| Variant | Context Data | When Raised |
|---|---|---|
| `Clvm(ValidationErr)` | Upstream CLVM error | `run_spendbundle()` or `validate_clvm_and_signature()` returns an error |
| `CoinNotFound(Bytes32)` | Coin ID | A spent coin is not in `ValidationContext.coin_records` or `ephemeral_coins` |
| `AlreadySpent(Bytes32)` | Coin ID | A coin in `coin_records` has `spent == true` |
| `DoubleSpend(Bytes32)` | Coin ID | The same coin appears as input more than once in a single spend bundle |
| `PuzzleHashMismatch(Bytes32)` | Coin ID | The puzzle reveal hash does not match the coin's `puzzle_hash` |
| `SignatureFailed` | (none) | BLS `aggregate_verify()` returns false for the bundle's signature |
| `ConservationViolation { input, output }` | Total amounts | Sum of spent coin amounts < sum of created coin amounts (fee would be negative) |
| `CostExceeded { limit, consumed }` | Cost values | CLVM execution cost exceeds `ValidationConfig.max_cost_per_spend` or `max_cost_per_block` |
| `Driver(DriverError)` | Upstream driver error | `chia-sdk-driver` returns an error during spend construction or puzzle parsing |

### Context Requirements

- All `Bytes32` fields MUST contain the coin ID (not puzzle hash, not parent ID) so the failing coin can be uniquely identified.
- `ConservationViolation` MUST report both the total input and total output amounts so the caller can compute the discrepancy.
- `CostExceeded` MUST report both the limit and the consumed cost so the caller can understand how far over the limit the execution went.
- `SignatureFailed` does not carry additional context because BLS aggregate verification is all-or-nothing -- if it fails, you cannot determine which individual signature was invalid without decomposing the bundle.

---

## Acceptance Criteria

- [ ] `ValidationError` enum exists in `src/consensus/error.rs` with exactly nine variants as listed above
- [ ] Each variant carries the specified context data (Bytes32 for coin IDs, u64 pair for conservation, Cost pair for cost)
- [ ] Pattern matching on all nine variants is exhaustive (a `match` without wildcard compiles)
- [ ] `CoinNotFound`, `AlreadySpent`, `DoubleSpend`, and `PuzzleHashMismatch` all carry `Bytes32` coin IDs
- [ ] `ConservationViolation` uses named fields `input` and `output` (not positional)
- [ ] `CostExceeded` uses named fields `limit` and `consumed` (not positional)

---

## Implementation Notes

- The `Clvm` variant wraps `chia_consensus::validation_error::ValidationErr` directly. This type may not implement `Clone`, so `ValidationError` should not derive `Clone` unless verified.
- The `Driver` variant uses `#[from]` for automatic conversion from `DriverError` via the `?` operator.
- The `Clvm` variant intentionally does NOT use `#[from]` because `ValidationErr` conversion may need manual wrapping in some code paths. See [API-006](API-006.md) for `From` impl details.
- `SignatureFailed` is intentionally context-free. If future requirements demand identifying which spend's signature failed, this variant can be extended to `SignatureFailed(Option<Bytes32>)` as a breaking change.

---

## Verification

### Test Plan

1. **Exhaustive pattern match test**:
   ```rust
   #[test]
   fn validation_error_variants_exhaustive() {
       fn check(e: ValidationError) {
           match e {
               ValidationError::Clvm(_) => {}
               ValidationError::CoinNotFound(_) => {}
               ValidationError::AlreadySpent(_) => {}
               ValidationError::DoubleSpend(_) => {}
               ValidationError::PuzzleHashMismatch(_) => {}
               ValidationError::SignatureFailed => {}
               ValidationError::ConservationViolation { input: _, output: _ } => {}
               ValidationError::CostExceeded { limit: _, consumed: _ } => {}
               ValidationError::Driver(_) => {}
           }
       }
   }
   ```
   - This MUST compile without warnings. Adding or removing a variant will cause a compile error.

2. **Context data verification**:
   - Construct each variant with test data and verify the context is preserved:
     ```rust
     let coin_id = Bytes32::from([0xAB; 32]);
     let err = ValidationError::CoinNotFound(coin_id);
     match err {
         ValidationError::CoinNotFound(id) => assert_eq!(id, coin_id),
         _ => panic!("wrong variant"),
     }
     ```

3. **Display output verification**:
   - Verify `format!("{}", err)` produces human-readable messages for each variant.
   - Verify coin IDs appear in hex in the display output.

4. **From<DriverError> conversion test**:
   - Construct a `DriverError` and convert it to `ValidationError` using `.into()` or `?`. Verify it produces the `Driver` variant.

---

## Source Citations

- [SPEC.md Section 5.1](../../../../resources/SPEC.md) — Complete `ValidationError` enum definition
- [SPEC.md Section 3.1](../../../../resources/SPEC.md) — ValidationError is L2-specific, wraps upstream errors
- [Chia L1 `validation_error.py`](https://github.com/Chia-Network/chia-blockchain/blob/main/chia/consensus/multiprocess_validation.py#L17) — Upstream error type wrapped by `Clvm` variant

---

## References

- [API-006](API-006.md) — thiserror implementation and Error/Display traits
- [API-004](API-004.md) — Errors carry context instead of looking it up (no storage access)
- [API-008](API-008.md) — Error type lives in `src/consensus/error.rs`
