diff --git a/crates/chia-consensus/src/spendbundle_conditions.rs b/crates/chia-consensus/src/spendbundle_conditions.rs index 95ac279d..1b952fbd 100644 --- a/crates/chia-consensus/src/spendbundle_conditions.rs +++ b/crates/chia-consensus/src/spendbundle_conditions.rs @@ -4,6 +4,7 @@ use crate::gen::conditions::{ }; use crate::gen::flags::MEMPOOL_MODE; use crate::gen::run_block_generator::subtract_cost; +use crate::gen::solution_generator::solution_generator; use crate::gen::validation_error::ValidationErr; use crate::spendbundle_validation::get_flags_for_height_and_constants; use chia_protocol::SpendBundle; @@ -30,12 +31,21 @@ pub fn get_conditions_from_spendbundle( let mut ret = SpendBundleConditions::default(); let mut state = ParseState::default(); - for coin_spend in &spend_bundle.coin_spends { - let byte_cost = (coin_spend.puzzle_reveal.len() + coin_spend.solution.len()) as u64 - * constants.cost_per_byte; - - subtract_cost(a, &mut cost_left, byte_cost)?; + let spends_info = spend_bundle.coin_spends.iter().map(|coin_spend| { + ( + coin_spend.coin, + &coin_spend.puzzle_reveal, + &coin_spend.solution, + ) + }); + // We don't pay the size cost (nor execution cost) of being wrapped by a + // quote (in solution_generator). + const QUOTE_BYTES: usize = 2; + let generator_length_without_quote = solution_generator(spends_info)?.len() - QUOTE_BYTES; + let byte_cost = generator_length_without_quote as u64 * constants.cost_per_byte; + subtract_cost(a, &mut cost_left, byte_cost)?; + for coin_spend in &spend_bundle.coin_spends { // process the spend let puz = node_from_bytes(a, coin_spend.puzzle_reveal.as_slice())?; let sol = node_from_bytes(a, coin_spend.solution.as_slice())?; @@ -74,6 +84,7 @@ mod tests { use super::*; use crate::allocator::make_allocator; use crate::gen::conditions::{ELIGIBLE_FOR_DEDUP, ELIGIBLE_FOR_FF}; + use crate::gen::run_block_generator::run_block_generator2; use chia_bls::Signature; use chia_protocol::CoinSpend; use chia_traits::Streamable; @@ -82,8 +93,8 @@ mod tests { use std::fs::read; #[rstest] - #[case("3000253", 8, 2, 46_860_870)] - #[case("1000101", 34, 15, 231_687_677)] + #[case("3000253", 8, 2, 51_216_870)] + #[case("1000101", 34, 15, 250_083_677)] fn test_get_conditions_from_spendbundle( #[case] filename: &str, #[case] spends: usize, @@ -108,6 +119,38 @@ mod tests { .fold(0, |sum, spend| sum + spend.create_coin.len()); assert_eq!(create_coins, additions); assert_eq!(conditions.cost, cost); + // Generate a block with the same spend bundle and compare its cost + let program_spends: Vec<_> = bundle + .coin_spends + .iter() + .map(|coin_spend| { + ( + coin_spend.coin, + &coin_spend.puzzle_reveal, + &coin_spend.solution, + ) + }) + .collect(); + let program = solution_generator(program_spends).expect("solution_generator failed"); + let blocks: &[&[u8]] = &[]; + let block_conds = run_block_generator2::<_, MempoolVisitor, _>( + &mut a, + program.as_slice(), + blocks, + 11_000_000_000, + MEMPOOL_MODE, + &TEST_CONSTANTS, + ) + .expect("run_block_generator2 failed"); + // The cost difference here is because get_conditions_from_spendbundle + // uses solution_generator which wraps the program in a quote, and we + // don't pay the size cost nor execution cost for that there. + const QUOTE_EXECUTION_COST: u64 = 20; + const QUOTE_BYTE_COST: u64 = 2 * TEST_CONSTANTS.cost_per_byte; + assert_eq!( + conditions.cost, + block_conds.cost - QUOTE_EXECUTION_COST - QUOTE_BYTE_COST + ); } #[rstest] @@ -117,7 +160,7 @@ mod tests { #[case] filename: &str, #[values(0, 1, 1_000_000, 5_000_000)] height: u32, ) { - let cost = 76_825_866; + let cost = 77_341_866; let spend = CoinSpend::from_bytes( &read(format!("../../ff-tests/{filename}.spend")).expect("read file"), ) @@ -306,12 +349,24 @@ mod tests { // block will likely be smaller because the compression makes it // smaller. let block_byte_cost = generator_buffer.len() as u64 * TEST_CONSTANTS.cost_per_byte; - let bundle_byte_cost = bundle + let program_spends: Vec<_> = bundle .coin_spends .iter() - .map(|s| s.puzzle_reveal.len() + s.solution.len()) - .sum::() as u64 - * TEST_CONSTANTS.cost_per_byte; + .map(|coin_spend| { + ( + coin_spend.coin, + &coin_spend.puzzle_reveal, + &coin_spend.solution, + ) + }) + .collect(); + const QUOTE_BYTES: usize = 2; + let generator_length_without_quote = solution_generator(program_spends) + .expect("solution_generator failed") + .len() + - QUOTE_BYTES; + let bundle_byte_cost = + generator_length_without_quote as u64 * TEST_CONSTANTS.cost_per_byte; println!("block_cost: {block_cost} bytes: {block_byte_cost}"); println!("bundle_cost: {} bytes: {bundle_byte_cost}", conditions.cost); assert!(conditions.cost - bundle_byte_cost <= block_cost - block_byte_cost); diff --git a/crates/chia-consensus/src/spendbundle_validation.rs b/crates/chia-consensus/src/spendbundle_validation.rs index afb5a50c..af069214 100644 --- a/crates/chia-consensus/src/spendbundle_validation.rs +++ b/crates/chia-consensus/src/spendbundle_validation.rs @@ -228,12 +228,15 @@ ff01\ coin_spends, aggregated_signature: G2Element::default(), }; - let result = validate_clvm_and_signature(&spend_bundle, 5526552044, &TEST_CONSTANTS, 236); - let Ok((conds, _, _)) = result else { - panic!("failed"); - }; - assert_eq!(conds.cost, 5526552044); - let result = validate_clvm_and_signature(&spend_bundle, 5526552043, &TEST_CONSTANTS, 236); + let expected_cost = 5_527_116_044; + let max_cost = expected_cost; + let test_height = 236; + let (conds, _, _) = + validate_clvm_and_signature(&spend_bundle, max_cost, &TEST_CONSTANTS, test_height) + .expect("validate_clvm_and_signature failed"); + assert_eq!(conds.cost, expected_cost); + let result = + validate_clvm_and_signature(&spend_bundle, max_cost - 1, &TEST_CONSTANTS, test_height); assert!(matches!(result, Err(ErrorCode::CostExceeded))); }