use std::collections::HashSet;
use chia_bls::BlsCache;
use chia_consensus::allocator::make_allocator;
use chia_consensus::flags::DONT_VALIDATE_SIGNATURE;
use chia_consensus::owned_conditions::OwnedSpendBundleConditions;
use chia_consensus::spendbundle_conditions::run_spendbundle;
use chia_consensus::spendbundle_validation::validate_clvm_and_signature;
use chia_protocol::{Bytes, Coin, SpendBundle};
use clvmr::LIMIT_HEAP;
use super::config::ValidationConfig;
use super::context::ValidationContext;
use super::error::ValidationError;
use super::result::SpendResult;
pub fn validate_spend_bundle(
bundle: &SpendBundle,
context: &ValidationContext,
config: &ValidationConfig,
_bls_cache: Option<&mut BlsCache>,
) -> Result<SpendResult, ValidationError> {
let mut seen_coin_ids = HashSet::new();
for spend in &bundle.coin_spends {
let coin_id = spend.coin.coin_id();
if !seen_coin_ids.insert(coin_id) {
return Err(ValidationError::DoubleSpend(coin_id));
}
}
for spend in &bundle.coin_spends {
let coin_id = spend.coin.coin_id();
match context.coin_records.get(&coin_id) {
Some(record) => {
if record.spent {
return Err(ValidationError::AlreadySpent(coin_id));
}
}
None => {
if !context.ephemeral_coins.contains(&coin_id) {
return Err(ValidationError::CoinNotFound(coin_id));
}
}
}
}
let max_cost = config.max_cost_per_block;
let consensus = context.constants.consensus();
let conditions: OwnedSpendBundleConditions = if config.flags & DONT_VALIDATE_SIGNATURE != 0 {
let mut a = make_allocator(LIMIT_HEAP);
let (sbc, _pkm_pairs) = run_spendbundle(
&mut a,
bundle,
max_cost,
context.height,
config.flags,
consensus,
)
.map_err(|e| ValidationError::Clvm(format!("{:?}", e)))?;
OwnedSpendBundleConditions::from(&a, sbc)
} else if let Some(cache) = _bls_cache {
let mut a = make_allocator(LIMIT_HEAP);
let (sbc, pkm_pairs) = run_spendbundle(
&mut a,
bundle,
max_cost,
context.height,
config.flags,
consensus,
)
.map_err(|e| ValidationError::Clvm(format!("{:?}", e)))?;
let pks_msgs: Vec<(chia_bls::PublicKey, Bytes)> = pkm_pairs;
let sig_valid = cache.aggregate_verify(
pks_msgs.iter().map(|(pk, msg)| (pk, msg.as_ref())),
&bundle.aggregated_signature,
);
if !sig_valid {
return Err(ValidationError::SignatureFailed);
}
OwnedSpendBundleConditions::from(&a, sbc)
} else {
let (owned_conditions, _validation_pairs, _duration) =
validate_clvm_and_signature(bundle, max_cost, consensus, context.height)
.map_err(|e| ValidationError::Clvm(format!("{:?}", e)))?;
owned_conditions
};
if conditions.cost > config.max_cost_per_block {
return Err(ValidationError::CostExceeded {
limit: config.max_cost_per_block,
consumed: conditions.cost,
});
}
let removals: Vec<Coin> = bundle.coin_spends.iter().map(|cs| cs.coin).collect();
let additions: Vec<Coin> = conditions
.spends
.iter()
.flat_map(|spend| {
let parent_id = spend.coin_id;
spend
.create_coin
.iter()
.map(move |cc| Coin::new(parent_id, cc.0, cc.1))
})
.collect();
let total_input: u64 = removals.iter().map(|c| c.amount).sum();
let total_output: u64 = additions.iter().map(|c| c.amount).sum();
if total_input < total_output {
return Err(ValidationError::ConservationViolation {
input: total_input,
output: total_output,
});
}
let fee = total_input - total_output;
Ok(SpendResult {
additions,
removals,
fee,
conditions,
})
}