# VAL-002: Delegates to chia-consensus run_spendbundle

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

---

## Summary

The core design principle of `dig-clvm` is to maximize reuse of the Chia crate ecosystem rather than reimplementing consensus logic. The single most critical instance of this principle is the delegation of CLVM execution and condition extraction to `chia_consensus::run_spendbundle()`. This function is the same Rust code path that Chia L1 full nodes use for both mempool validation and block validation.

On Chia L1, the Rust function `run_spendbundle()` is called from Python via `chia_rs`. The mempool path reaches it through [`validate_clvm_and_signature()`](https://github.com/Chia-Network/chia-blockchain/blob/main/chia/full_node/mempool_manager.py#L445) at `mempool_manager.py:445`, while the block validation path reaches it through [`_run_block()`](https://github.com/Chia-Network/chia-blockchain/blob/main/chia/consensus/multiprocess_validation.py#L62) at `multiprocess_validation.py:62`. By calling the same Rust function directly, `dig-clvm` achieves L1 parity by construction for CLVM execution, condition parsing, and cost accounting.

This requirement exists to prevent a hand-rolled CLVM execution loop that would be error-prone, diverge from L1 behavior over time, and duplicate significant complexity already handled by `chia-consensus`. The `run_spendbundle()` function handles: allocator creation, dialect setup, per-spend execution with cost tracking, condition parsing, `SpendVisitor` hooks (including `MempoolVisitor`), and aggregation into `SpendBundleConditions`.

---

## Specification

### Delegation Requirement

`validate_spend_bundle()` MUST call `chia_consensus::spendbundle_conditions::run_spendbundle()` (or its signature-validating wrapper `validate_clvm_and_signature()`) for all CLVM execution and condition extraction. It MUST NOT implement its own CLVM execution loop.

### run_spendbundle Signature (from chia-consensus)

```rust
pub fn run_spendbundle(
    allocator: &mut Allocator,
    bundle: &SpendBundle,
    max_cost: u64,
    height: u32,
    constants: &ConsensusConstants,
    flags: u32,
) -> Result<SpendBundleConditions, ValidationErr>;
```

### How dig-clvm Calls It

```rust
// Inside validate_spend_bundle():
let mut allocator = make_allocator(config.flags);
let consensus_constants = context.constants.consensus();

let conditions = run_spendbundle(
    &mut allocator,
    bundle,
    config.max_cost_per_block,  // L2 cost limit
    context.height,
    consensus_constants,
    config.flags,               // MEMPOOL_MODE, etc.
)?;
```

### What run_spendbundle Handles Internally

1. Creates `ChiaDialect` with the provided flags.
2. For each `CoinSpend` in the bundle:
   - Deserializes `puzzle_reveal` and `solution` into the allocator.
   - Runs `puzzle_reveal` with `solution` as the environment via `clvmr::run_program()`.
   - Parses the CLVM output list into typed conditions.
   - Tracks per-spend cost and aggregates into total cost.
3. If `flags` includes `MEMPOOL_MODE`, uses `MempoolVisitor` which rejects unknown opcodes and applies stricter cost rules.
4. Validates announcement matching (CREATE_ANNOUNCE vs ASSERT_ANNOUNCE).
5. Returns `SpendBundleConditions` containing all parsed conditions, total cost, and per-spend breakdowns.

### What dig-clvm MUST NOT Do

- MUST NOT call `clvmr::run_program()` directly for spend bundle validation.
- MUST NOT implement its own condition parser.
- MUST NOT implement its own cost accounting for CLVM execution.
- MUST NOT implement its own announcement matching.

---

## Acceptance Criteria

- [ ] The source code of `validate_spend_bundle()` contains a call to `run_spendbundle()` or `validate_clvm_and_signature()` from `chia-consensus`.
- [ ] There is no direct call to `clvmr::run_program()` within the validation path of `validate_spend_bundle()`.
- [ ] There is no hand-rolled condition parsing loop in `validate_spend_bundle()`.
- [ ] The `chia-consensus` crate is listed as a dependency in `Cargo.toml`.
- [ ] The `flags` parameter from `ValidationConfig` is passed through to `run_spendbundle()`.
- [ ] The `max_cost_per_block` from `ValidationConfig` is passed as the `max_cost` parameter to `run_spendbundle()`.
- [ ] CLVM errors from `run_spendbundle()` are mapped to `ValidationError::Clvm`.

---

## Implementation Notes

- **Primary file:** `src/consensus/validate.rs`
- **Import:** `use chia_consensus::spendbundle_conditions::run_spendbundle;`
- **Allocator creation:** Use `chia_consensus::make_allocator(flags)` to create a properly configured allocator. This ensures `LIMIT_HEAP` and other flag-dependent behavior is correct.
- **ConsensusConstants:** Obtained from `context.constants.consensus()` which unwraps the `dig-constants::NetworkConstants` wrapper to get the underlying `chia-consensus::ConsensusConstants`.
- **Error mapping:** `run_spendbundle()` returns `Result<SpendBundleConditions, ValidationErr>`. The `ValidationErr` should be wrapped in `ValidationError::Clvm(err)`.
- **Edge case:** If `config.flags` includes `MEMPOOL_MODE`, `run_spendbundle()` automatically uses `MempoolVisitor`. No additional logic is needed in `dig-clvm` for this.
- **Dependency:** This is the foundational delegation that VAL-007 (cost), VAL-009 (conditions), and VAL-013 (mempool mode) all rely on.

---

## Verification

### Test Plan

| Test | Type | Description | Expected Result |
|------|------|-------------|-----------------|
| Code review: delegation | Code review | Grep `validate.rs` for `run_spendbundle` call; confirm no `run_program` call | `run_spendbundle` found; no `run_program` |
| Code review: no hand-rolled conditions | Code review | Search for manual condition opcode parsing in validation path | No manual parsing found |
| Valid bundle executes CLVM | Simulator test | Build standard spend, validate, check conditions are populated | `SpendResult.conditions` contains parsed conditions |
| CLVM error propagation | Simulator test | Build bundle with invalid CLVM program (e.g., non-list output) | Returns `Err(ValidationError::Clvm(_))` |
| Flags pass-through | Simulator test | Validate with `MEMPOOL_MODE` flag, confirm stricter rules apply | Unknown opcodes rejected |
| Cost pass-through | Simulator test | Set very low `max_cost_per_block`, validate expensive bundle | Returns `Err(ValidationError::CostExceeded { .. })` or `Err(ValidationError::Clvm(_))` |

---

## Source Citations

- **SPEC.md Section 5.1** (line 374): `validate_spend_bundle()` documentation states "Internally calls chia_consensus::run_spendbundle() for CLVM execution and condition extraction."
- **SPEC.md Section 6.1** (lines 645-653): Path A step 2 shows `chia-consensus::run_spendbundle()` as the CLVM execution step.
- **SPEC.md Section 2.5** (lines 95-113): `chia-consensus` crate inventory with `run_spendbundle` API.
- **SPEC.md Section 9.2** (lines 939-947): Design decision "Why `chia-consensus` for Validation Instead of Hand-Rolled."
- **L1 reference — `validate_clvm_and_signature()`:** [`mempool_manager.py:445`](https://github.com/Chia-Network/chia-blockchain/blob/main/chia/full_node/mempool_manager.py#L445)
- **L1 reference — `_run_block()`:** [`multiprocess_validation.py:62`](https://github.com/Chia-Network/chia-blockchain/blob/main/chia/consensus/multiprocess_validation.py#L62)

---

## References

- [VAL-001](VAL-001.md) — Entry point signature (calls this delegation)
- [VAL-007](VAL-007.md) — Cost limit enforcement (relies on cost from `run_spendbundle`)
- [VAL-009](VAL-009.md) — Condition validation delegated to chia-consensus
- [VAL-013](VAL-013.md) — MEMPOOL_MODE stricter rules (via `MempoolVisitor` in `run_spendbundle`)
- [VAL-014](VAL-014.md) — SpendResult output (conditions come from `run_spendbundle` output)
