Skip to main content

dig_clvm/consensus/
validate.rs

1//! Spend bundle validation.
2//!
3//! Orchestrates `chia-consensus` with L2-specific structural checks
4//! and cost enforcement.
5
6use std::collections::HashSet;
7
8use chia_bls::BlsCache;
9use chia_consensus::allocator::make_allocator;
10use chia_consensus::flags::DONT_VALIDATE_SIGNATURE;
11use chia_consensus::owned_conditions::OwnedSpendBundleConditions;
12use chia_consensus::spendbundle_conditions::run_spendbundle;
13use chia_consensus::spendbundle_validation::validate_clvm_and_signature;
14use chia_protocol::{Bytes, Coin, SpendBundle};
15use clvmr::LIMIT_HEAP;
16
17use super::config::ValidationConfig;
18use super::context::ValidationContext;
19use super::error::ValidationError;
20use super::result::SpendResult;
21
22/// Validate a spend bundle against L2 consensus rules.
23///
24/// 1. Structural checks (duplicates, coin existence)
25/// 2. CLVM execution + condition extraction + BLS sig verification
26///    (delegated to chia-consensus)
27/// 3. Cost enforcement against L2 limits
28/// 4. Conservation check (inputs >= outputs + fee)
29/// 5. Extract additions/removals into SpendResult
30pub fn validate_spend_bundle(
31    bundle: &SpendBundle,
32    context: &ValidationContext,
33    config: &ValidationConfig,
34    _bls_cache: Option<&mut BlsCache>,
35) -> Result<SpendResult, ValidationError> {
36    // ── Step 1: Structural checks ──
37
38    // Check for duplicate spends
39    let mut seen_coin_ids = HashSet::new();
40    for spend in &bundle.coin_spends {
41        let coin_id = spend.coin.coin_id();
42        if !seen_coin_ids.insert(coin_id) {
43            return Err(ValidationError::DoubleSpend(coin_id));
44        }
45    }
46
47    // Check all coins exist and are unspent
48    for spend in &bundle.coin_spends {
49        let coin_id = spend.coin.coin_id();
50        match context.coin_records.get(&coin_id) {
51            Some(record) => {
52                if record.spent {
53                    return Err(ValidationError::AlreadySpent(coin_id));
54                }
55            }
56            None => {
57                if !context.ephemeral_coins.contains(&coin_id) {
58                    return Err(ValidationError::CoinNotFound(coin_id));
59                }
60            }
61        }
62    }
63
64    // ── Step 2: CLVM execution + conditions + BLS verification ──
65    //
66    // Two paths depending on whether signature verification is requested:
67    // - With signatures: validate_clvm_and_signature() handles everything
68    //   including allocator creation, CLVM execution, condition parsing,
69    //   puzzle hash verification, and BLS aggregate verify.
70    // - Without signatures (DONT_VALIDATE_SIGNATURE flag): run_spendbundle()
71    //   directly, which skips the BLS pairing check.
72
73    let max_cost = config.max_cost_per_block;
74    let consensus = context.constants.consensus();
75
76    let conditions: OwnedSpendBundleConditions = if config.flags & DONT_VALIDATE_SIGNATURE != 0 {
77        // Skip BLS verification — use run_spendbundle with the flag
78        let mut a = make_allocator(LIMIT_HEAP);
79        let (sbc, _pkm_pairs) = run_spendbundle(
80            &mut a,
81            bundle,
82            max_cost,
83            context.height,
84            config.flags,
85            consensus,
86        )
87        .map_err(|e| ValidationError::Clvm(format!("{:?}", e)))?;
88        OwnedSpendBundleConditions::from(&a, sbc)
89    } else if let Some(cache) = _bls_cache {
90        // BLS verification WITH cache — run CLVM first, then use
91        // BlsCache::aggregate_verify() which checks cache before
92        // computing expensive pairings.
93        let mut a = make_allocator(LIMIT_HEAP);
94        let (sbc, pkm_pairs) = run_spendbundle(
95            &mut a,
96            bundle,
97            max_cost,
98            context.height,
99            config.flags,
100            consensus,
101        )
102        .map_err(|e| ValidationError::Clvm(format!("{:?}", e)))?;
103
104        // Use BlsCache for aggregate verification — cached pairings
105        // are reused, new pairings are stored for future calls.
106        let pks_msgs: Vec<(chia_bls::PublicKey, Bytes)> = pkm_pairs;
107        let sig_valid = cache.aggregate_verify(
108            pks_msgs.iter().map(|(pk, msg)| (pk, msg.as_ref())),
109            &bundle.aggregated_signature,
110        );
111        if !sig_valid {
112            return Err(ValidationError::SignatureFailed);
113        }
114
115        OwnedSpendBundleConditions::from(&a, sbc)
116    } else {
117        // Full validation without cache — validate_clvm_and_signature
118        // handles everything including BLS aggregate verify.
119        let (owned_conditions, _validation_pairs, _duration) =
120            validate_clvm_and_signature(bundle, max_cost, consensus, context.height)
121                .map_err(|e| ValidationError::Clvm(format!("{:?}", e)))?;
122        owned_conditions
123    };
124
125    // ── Step 3: Cost enforcement ──
126    if conditions.cost > config.max_cost_per_block {
127        return Err(ValidationError::CostExceeded {
128            limit: config.max_cost_per_block,
129            consumed: conditions.cost,
130        });
131    }
132
133    // ── Step 4: Extract additions and removals ──
134    let removals: Vec<Coin> = bundle.coin_spends.iter().map(|cs| cs.coin).collect();
135
136    let additions: Vec<Coin> = conditions
137        .spends
138        .iter()
139        .flat_map(|spend| {
140            let parent_id = spend.coin_id;
141            spend
142                .create_coin
143                .iter()
144                .map(move |cc| Coin::new(parent_id, cc.0, cc.1))
145        })
146        .collect();
147
148    // ── Step 5: Conservation check ──
149    let total_input: u64 = removals.iter().map(|c| c.amount).sum();
150    let total_output: u64 = additions.iter().map(|c| c.amount).sum();
151
152    if total_input < total_output {
153        return Err(ValidationError::ConservationViolation {
154            input: total_input,
155            output: total_output,
156        });
157    }
158
159    let fee = total_input - total_output;
160
161    Ok(SpendResult {
162        additions,
163        removals,
164        fee,
165        conditions,
166    })
167}