# BLK-008: Same Checks as validate_spend_bundle

> **Authoritative requirement:** [BLK-008](../NORMATIVE.md#BLK-008)
> **Verification:** [VERIFICATION.md](../VERIFICATION.md)
> **Tracking:** [TRACKING.yaml](../TRACKING.yaml)
> **Spec reference:** [SPEC.md](../../../../resources/SPEC.md) — Section 6.1 Path B

---

## Summary

`validate_block` MUST apply the same validation checks as `validate_spend_bundle` to ensure that blocks and individual spend bundles are held to identical consensus rules. A block is not a relaxed execution environment — it is a batch of transactions that must each individually satisfy the same cost enforcement (VAL-007), condition validation (VAL-009), BLS signature verification (VAL-012), and conservation of value (VAL-011) rules that apply to individual spend bundles. Invalid blocks must be rejected with the same error types.

This requirement prevents a class of bugs where a transaction that would be rejected by `validate_spend_bundle` could sneak into a block by exploiting differences in the block-level validation path. On Chia L1, the block body validator in `validate_block_body()` applies the same checks as mempool validation, ensuring consensus consistency between the two paths. The block generator produces a single CLVM program that, when executed, yields all the coin spends in the block, and the output is validated with the same rules.

The key difference between the two paths is operational, not logical: `validate_block` receives a serialized generator program (from `build_block_generator`) and executes it to produce the spend bundles, whereas `validate_spend_bundle` receives a pre-formed `SpendBundle` directly. Once the spends are extracted from the generator output, the same validation pipeline applies. Both paths converge on the same set of checks, the same error types, and the same output structures.

---

## Specification

### Shared Validation Pipeline

Both `validate_spend_bundle` and `validate_block` execute the following checks in order:

| Step | Check | Requirement | Error |
|------|-------|-------------|-------|
| 1 | Coin existence | VAL-004 | CoinNotFound |
| 2 | Already-spent | VAL-005 | CoinAlreadySpent |
| 3 | Puzzle hash match | VAL-006 | PuzzleHashMismatch |
| 4 | CLVM execution + cost | VAL-007, VAL-008 | CostExceeded, ClvmError |
| 5 | Condition validation | VAL-009 | ConditionError |
| 6 | BLS signature | VAL-012 | SignatureFailed |
| 7 | Conservation | VAL-011 | ConservationViolation |

### validate_block Flow

```rust
/// Validate a full block by running its generator program and
/// applying the same checks as validate_spend_bundle.
pub fn validate_block(
    generator: &[u8],           // Serialized CLVM generator program
    context: &ValidationContext, // Coin records, height, timestamp, constants
    config: &ValidationConfig,   // Flags (MEMPOOL_MODE, DONT_VALIDATE_SIGNATURE)
    cache: Option<&mut BlsCache>,
) -> Result<BlockResult, ValidationError> {
    // Step 0: Execute the generator program to extract coin spends.
    // Uses run_block_generator2() from chia-consensus.
    let spend_bundles = run_generator(generator, context, config)?;

    // Steps 1-7: Apply the SAME validation checks as validate_spend_bundle
    // to each spend bundle extracted from the generator output.
    let mut all_additions = Vec::new();
    let mut all_removals = Vec::new();
    let mut total_fee = 0u64;
    let mut ephemeral = context.ephemeral_coins.clone();

    for bundle in &spend_bundles {
        // Each bundle goes through the full validation pipeline
        let bundle_context = ValidationContext {
            coin_records: context.coin_records.clone(),
            ephemeral_coins: ephemeral.clone(),
            height: context.height,
            timestamp: context.timestamp,
            constants: context.constants.clone(),
        };

        let result = validate_spend_bundle(bundle, &bundle_context, config, cache)?;

        // Track ephemeral coins for subsequent bundles
        for coin in &result.additions {
            ephemeral.insert(coin.coin_id());
        }

        all_additions.extend(result.additions);
        all_removals.extend(result.removals);
        total_fee += result.fee;
    }

    Ok(BlockResult {
        additions: all_additions,
        removals: all_removals,
        fee: total_fee,
    })
}
```

### Error Type Consistency

Both paths use the same `ValidationError` enum:

```rust
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ValidationError {
    CoinNotFound(Bytes32),
    CoinAlreadySpent(Bytes32),
    PuzzleHashMismatch { coin_id: Bytes32, expected: Bytes32, actual: Bytes32 },
    CostExceeded { cost: u64, max: u64 },
    ClvmError(String),
    ConditionError(String),
    SignatureFailed,
    ConservationViolation { input_total: u128, output_total: u128 },
    GeneratorError(String),  // Additional: generator execution failure
}
```

### No Relaxed Checks

The following anti-patterns are explicitly forbidden:

```rust
// WRONG: Skipping BLS in block validation
fn validate_block(...) {
    // Do NOT skip signature verification for blocks
    // unless DONT_VALIDATE_SIGNATURE flag is explicitly set
}

// WRONG: Different cost limits for blocks
fn validate_block(...) {
    // Do NOT use a different max_block_cost for blocks vs bundles
    // Both use constants.max_block_cost_clvm()
}

// WRONG: Ignoring condition failures in blocks
fn validate_block(...) {
    // Do NOT catch and ignore condition errors for block spends
    // Invalid conditions in a block = invalid block
}
```

---

## Acceptance Criteria

- [ ] `validate_block` applies coin existence checks (VAL-004) to all spends extracted from the generator.
- [ ] `validate_block` applies BLS signature verification (VAL-012) unless `DONT_VALIDATE_SIGNATURE` flag is set.
- [ ] `validate_block` enforces the same CLVM cost limits (VAL-007) as `validate_spend_bundle`.
- [ ] `validate_block` performs the conservation check (VAL-011) for each bundle in the block.
- [ ] `validate_block` returns the same `ValidationError` variants as `validate_spend_bundle` for the same types of failures.
- [ ] A spend bundle that fails `validate_spend_bundle` also fails when included in a block validated by `validate_block`.

---

## Implementation Notes

- **Primary file:** `src/consensus/validate_block.rs` (block validation), `src/consensus/validate.rs` (shared validation logic).
- **Code reuse:** The implementation SHOULD share validation logic between `validate_block` and `validate_spend_bundle` rather than duplicating it. Shared functions like `check_conservation`, `verify_bls_signature`, `check_coin_existence` should be called from both paths.
- **Generator execution:** The additional step in `validate_block` is running the generator program through `run_block_generator2()` from `chia-consensus`. This step can produce a `GeneratorError` that has no counterpart in `validate_spend_bundle`.
- **Ephemeral coin tracking:** `validate_block` must track ephemeral coins across bundles within the block (VAL-010). Each bundle's additions are added to the ephemeral set before validating the next bundle.
- **Aggregate cost:** The total CLVM cost across all bundles in a block must not exceed `max_block_cost_clvm`. This is an additional block-level constraint beyond per-bundle cost limits.
- **Flag forwarding:** `MEMPOOL_MODE` and `DONT_VALIDATE_SIGNATURE` flags apply identically in both paths.

---

## Verification

### Test Plan

| Test | Type | Description | Expected Result |
|------|------|-------------|-----------------|
| Valid block passes | Simulator test | Build generator from valid bundles, validate_block | Success with correct additions/removals/fee |
| Invalid coin in block | Simulator test | Block with non-existent coin | Err(CoinNotFound) — same as validate_spend_bundle |
| Cost exceeded in block | Simulator test | Block with over-cost CLVM program | Err(CostExceeded) — same error type |
| Bad signature in block | Simulator test | Block with tampered signature | Err(SignatureFailed) — same error type |
| Conservation violation in block | Simulator test | Block with value-creating spend | Err(ConservationViolation) — same error |
| Parity: bundle reject = block reject | Simulator test | Bundle that fails validate_spend_bundle, include in block | validate_block also rejects |
| Flag forwarding | Unit test | MEMPOOL_MODE and DONT_VALIDATE_SIGNATURE in block validation | Same effect as in spend bundle validation |

---

## Source Citations

- **SPEC.md Section 6.1 Path B**: "validate_block MUST apply same cost enforcement (VAL-007), condition validation (VAL-009), BLS verification (VAL-012), conservation (VAL-011)."
- **SPEC.md Section 6.1**: Shared validation pipeline steps for both paths.
- **L1 reference — validate_block_body:** [`block_body_validation.py`](https://github.com/Chia-Network/chia-blockchain/blob/main/chia/consensus/block_body_validation.py) — applies same checks as mempool validation.
- **L1 reference — multiprocess_validation.py:** Block validation using run_block_generator2.

---

## References

- [VAL-007](../../validation/specs/VAL-007.md) — Cost enforcement (shared check)
- [VAL-009](../../validation/specs/VAL-009.md) — Condition validation (shared check)
- [VAL-011](../../validation/specs/VAL-011.md) — Conservation check (shared check)
- [VAL-012](../../validation/specs/VAL-012.md) — BLS signature verification (shared check)
- [BLK-009](BLK-009.md) — Round-trip consistency (validate_block output must match per-bundle)
- [PAR-010](../../parity/specs/PAR-010.md) — Block generator parity with L1
