# VAL-007: Cost Limit Enforcement

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

---

## Summary

CLVM execution is a Turing-complete computation that must be bounded to prevent denial-of-service attacks. Every CLVM operation has an associated cost, and the total cost of executing all spends in a bundle must not exceed a configured maximum. `validate_spend_bundle()` MUST enforce this limit by checking the total cost reported by `run_spendbundle()` against `config.max_cost_per_block` and rejecting bundles that exceed it.

On Chia L1, the cost limit is enforced at multiple levels. The per-spend limit is `MAX_BLOCK_COST_CLVM = 11_000_000_000` (defined at [`default_constants.py:68`](https://github.com/Chia-Network/chia-blockchain/blob/main/chia/consensus/default_constants.py#L68)), which is also the per-block limit on L1 since each block contains at most one generator program. On DIG L2, the per-spend limit matches L1 (11B) but the per-block limit is much higher (550B, or 50x L1) to accommodate higher throughput. Both limits are configurable through `ValidationConfig`.

The cost model includes CLVM opcode costs, condition costs (AGG_SIG_COST = 1,200,000 per signature condition as defined at [`condition_costs.py:8`](https://github.com/Chia-Network/chia-blockchain/blob/main/chia/consensus/condition_costs.py#L8), CREATE_COIN_COST = 1,800,000 at [`:9`](https://github.com/Chia-Network/chia-blockchain/blob/main/chia/consensus/condition_costs.py#L9), GENERIC_CONDITION_COST = 500 at [`:13`](https://github.com/Chia-Network/chia-blockchain/blob/main/chia/consensus/condition_costs.py#L13)), and serialization costs. All of these are computed by `chia-consensus` during `run_spendbundle()` execution.

---

## Specification

### Cost Enforcement Algorithm

After `run_spendbundle()` returns successfully:

1. Read `conditions.cost` from the returned `SpendBundleConditions`.
2. Compare against `config.max_cost_per_block`.
3. If `conditions.cost > config.max_cost_per_block`, return `Err(ValidationError::CostExceeded { limit: config.max_cost_per_block, consumed: conditions.cost })`.

```rust
fn check_cost_limit(
    conditions: &SpendBundleConditions,
    config: &ValidationConfig,
) -> Result<(), ValidationError> {
    if conditions.cost > config.max_cost_per_block {
        return Err(ValidationError::CostExceeded {
            limit: config.max_cost_per_block,
            consumed: conditions.cost,
        });
    }
    Ok(())
}
```

### Two-Level Cost Limiting

Cost is enforced at two levels:

1. **Per-spend limit** (`config.max_cost_per_spend`, default 11B): Enforced by `run_spendbundle()` internally. Each individual puzzle execution is bounded by this limit. If a single spend exceeds it, `run_spendbundle()` returns an error that maps to `ValidationError::Clvm`.

2. **Per-block limit** (`config.max_cost_per_block`, default 550B): Enforced by `dig-clvm` after `run_spendbundle()` returns. The total cost across all spends in the bundle must not exceed this limit.

Note: `run_spendbundle()` receives `max_cost_per_block` as its `max_cost` parameter, so it will also abort early if the aggregate cost exceeds the limit during execution. The post-execution check in `dig-clvm` serves as an explicit guard with a specific error type.

### Error Details

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

Both `limit` and `consumed` are `Cost` (which is `u64`), allowing the caller to see exactly how much over the limit the bundle was.

### Cost Type

```rust
pub type Cost = u64;  // from clvmr
```

---

## Acceptance Criteria

- [ ] A bundle whose total CLVM cost exceeds `config.max_cost_per_block` is rejected with `ValidationError::CostExceeded`.
- [ ] The `CostExceeded` error contains both the `limit` and `consumed` values.
- [ ] A bundle whose cost is exactly equal to `config.max_cost_per_block` passes this check.
- [ ] A bundle whose cost is below `config.max_cost_per_block` passes this check.
- [ ] The `max_cost_per_block` value from `ValidationConfig` is used (not a hardcoded constant).
- [ ] Custom cost limits can be set via `ValidationConfig` and are honored.

---

## Implementation Notes

- **Primary file:** `src/consensus/validate.rs`
- **Position in flow:** Step 3 of the validation flow, immediately after CLVM execution (step 2).
- **Cost source:** The cost comes from `SpendBundleConditions.cost`, which is computed by `chia-consensus` during `run_spendbundle()`. `dig-clvm` does not compute cost independently.
- **Passing max_cost to run_spendbundle:** The `max_cost` parameter to `run_spendbundle()` should be `config.max_cost_per_block`. This causes `run_spendbundle()` to abort early if cost is exceeded during execution, rather than wasting resources. The post-check is still needed to produce the specific `CostExceeded` error variant.
- **Edge case:** If `config.max_cost_per_block` is set to 0, every non-empty bundle will be rejected.
- **Edge case:** If `config.max_cost_per_block` is set to `u64::MAX`, the cost check is effectively disabled.
- **Edge case:** Overflow is not a concern since `Cost` is `u64` and the maximum possible cost is bounded by the total number of CLVM operations, which cannot overflow a 64-bit integer.

---

## Verification

### Test Plan

| Test | Type | Description | Expected Result |
|------|------|-------------|-----------------|
| Cost exceeded rejection | Simulator test | Build expensive CLVM program (e.g., deep recursion), set low `max_cost_per_block` | `Err(ValidationError::CostExceeded { .. })` |
| Cost within limit | Simulator test | Build standard spend, use default config | Passes cost check |
| Custom limit honored | Simulator test | Set `max_cost_per_block = 1_000_000`, validate standard spend | Rejected (standard spends cost more than 1M) |
| Boundary: exactly at limit | Simulator test | Determine exact cost of a spend, set limit to that value | Passes (equal is allowed) |
| Error fields correct | Simulator test | Trigger cost exceeded, verify `limit` and `consumed` values | `limit` matches config, `consumed` matches actual cost |
| Default config limits | Unit test | Verify `ValidationConfig::default().max_cost_per_block == 550_000_000_000` | Assertion passes |

---

## Source Citations

- **SPEC.md Section 5.1** (lines 483-508): `ValidationConfig` definition with `max_cost_per_spend` and `max_cost_per_block`.
- **SPEC.md Section 5.1** (lines 559-562): Cost constants: `L1_MAX_COST_PER_SPEND = 11B`, `L2_MAX_COST_PER_BLOCK = 550B`.
- **SPEC.md Section 5.1** (line 553): `ValidationError::CostExceeded { limit, consumed }` definition.
- **SPEC.md Section 6.1** (lines 655-656): Step 3 cost enforcement: "conditions.cost <= config.max_cost_per_block."
- **L1 reference — MAX_BLOCK_COST_CLVM:** [`default_constants.py:68`](https://github.com/Chia-Network/chia-blockchain/blob/main/chia/consensus/default_constants.py#L68)
- **L1 reference — condition costs:** [`condition_costs.py:8-13`](https://github.com/Chia-Network/chia-blockchain/blob/main/chia/consensus/condition_costs.py#L8) — AGG_SIG=1.2M, CREATE_COIN=1.8M, GENERIC=500.

---

## References

- [VAL-002](VAL-002.md) — Delegates to run_spendbundle (which computes cost)
- [VAL-008](VAL-008.md) — Default cost constants (defines the default values)
- [VAL-013](VAL-013.md) — MEMPOOL_MODE (may affect cost accounting strictness)
- [VAL-001](VAL-001.md) — Entry point signature (config parameter carries cost limits)
