use crate::conditions::{
ELIGIBLE_FOR_DEDUP, MempoolVisitor, ParseState, SpendBundleConditions, process_single_spend,
validate_conditions,
};
use crate::consensus_constants::ConsensusConstants;
use crate::flags::{COMPUTE_FINGERPRINT, DONT_VALIDATE_SIGNATURE, MEMPOOL_MODE};
use crate::puzzle_fingerprint::compute_puzzle_fingerprint;
use crate::run_block_generator::subtract_cost;
use crate::solution_generator::calculate_generator_length;
use crate::spendbundle_validation::get_flags_for_height_and_constants;
use crate::validation_error::ErrorCode;
use crate::validation_error::ValidationErr;
use chia_bls::PublicKey;
use chia_protocol::{Bytes, SpendBundle};
use clvm_utils::tree_hash;
use clvmr::allocator::Allocator;
use clvmr::chia_dialect::ChiaDialect;
use clvmr::reduction::Reduction;
use clvmr::run_program::run_program;
use clvmr::serde::node_from_bytes;
const QUOTE_BYTES: usize = 2;
pub fn get_conditions_from_spendbundle(
a: &mut Allocator,
spend_bundle: &SpendBundle,
max_cost: u64,
prev_tx_height: u32,
constants: &ConsensusConstants,
) -> Result<SpendBundleConditions, ValidationErr> {
let flags = get_flags_for_height_and_constants(prev_tx_height, constants);
Ok(run_spendbundle(
a,
spend_bundle,
max_cost,
flags | MEMPOOL_MODE | DONT_VALIDATE_SIGNATURE,
constants,
)?
.0)
}
#[allow(clippy::type_complexity)]
pub fn run_spendbundle(
a: &mut Allocator,
spend_bundle: &SpendBundle,
max_cost: u64,
flags: u32,
constants: &ConsensusConstants,
) -> Result<(SpendBundleConditions, Vec<(PublicKey, Bytes)>), ValidationErr> {
let mut cost_left = max_cost;
let dialect = ChiaDialect::new(flags);
let mut ret = SpendBundleConditions::default();
let mut state = ParseState::default();
let generator_length_without_quote =
calculate_generator_length(&spend_bundle.coin_spends) - 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 {
let puz = node_from_bytes(a, coin_spend.puzzle_reveal.as_slice())?;
let sol = node_from_bytes(a, coin_spend.solution.as_slice())?;
let parent = a.new_atom(coin_spend.coin.parent_coin_info.as_slice())?;
let amount = a.new_number(coin_spend.coin.amount.into())?;
let Reduction(clvm_cost, conditions) = run_program(a, &dialect, puz, sol, cost_left)?;
ret.execution_cost += clvm_cost;
subtract_cost(a, &mut cost_left, clvm_cost)?;
let buf = tree_hash(a, puz);
if coin_spend.coin.puzzle_hash != buf.into() {
return Err(ValidationErr(puz, ErrorCode::WrongPuzzleHash));
}
let puzzle_hash = a.new_atom(&buf)?;
let spend = process_single_spend::<MempoolVisitor>(
a,
&mut ret,
&mut state,
parent,
puzzle_hash,
amount,
conditions,
flags,
&mut cost_left,
clvm_cost,
constants,
)?;
if (spend.flags & ELIGIBLE_FOR_DEDUP) != 0 && (flags & COMPUTE_FINGERPRINT) != 0 {
spend.fingerprint = compute_puzzle_fingerprint(a, conditions)?;
}
}
validate_conditions(a, &ret, &state, a.nil(), flags)?;
assert!(max_cost >= cost_left);
ret.cost = max_cost - cost_left;
Ok((ret, state.pkm_pairs))
}
#[cfg(test)]
mod tests {
use crate::consensus_constants::TEST_CONSTANTS;
use super::*;
use crate::allocator::make_allocator;
use crate::conditions::{ELIGIBLE_FOR_DEDUP, ELIGIBLE_FOR_FF};
use crate::run_block_generator::run_block_generator2;
use crate::solution_generator::solution_generator;
use chia_bls::Signature;
use chia_protocol::CoinSpend;
use chia_traits::Streamable;
use clvmr::chia_dialect::LIMIT_HEAP;
use rstest::rstest;
use std::fs::read;
const QUOTE_EXECUTION_COST: u64 = 20;
const QUOTE_BYTES_COST: u64 = QUOTE_BYTES as u64 * TEST_CONSTANTS.cost_per_byte;
#[rstest]
#[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,
#[case] additions: usize,
#[values(0, 1, 1_000_000, 5_000_000)] prev_tx_height: u32,
#[case] cost: u64,
) {
let bundle = SpendBundle::from_bytes(
&read(format!("../../test-bundles/{filename}.bundle")).expect("read file"),
)
.expect("parse bundle");
let mut a = make_allocator(LIMIT_HEAP);
let conditions =
get_conditions_from_spendbundle(&mut a, &bundle, cost, prev_tx_height, &TEST_CONSTANTS)
.expect("get_conditions_from_spendbundle");
assert_eq!(conditions.spends.len(), spends);
let create_coins = conditions
.spends
.iter()
.fold(0, |sum, spend| sum + spend.create_coin.len());
assert_eq!(create_coins, additions);
assert_eq!(conditions.cost, cost);
let program_spends = bundle.coin_spends.iter().map(|coin_spend| {
(
coin_spend.coin,
&coin_spend.puzzle_reveal,
&coin_spend.solution,
)
});
let program = solution_generator(program_spends).expect("solution_generator failed");
let blocks: &[&[u8]] = &[];
let block_conds = run_block_generator2(
&mut a,
program.as_slice(),
blocks,
11_000_000_000,
MEMPOOL_MODE | DONT_VALIDATE_SIGNATURE,
&Signature::default(),
None,
&TEST_CONSTANTS,
)
.expect("run_block_generator2 failed");
assert_eq!(
conditions.cost,
block_conds.cost - QUOTE_EXECUTION_COST - QUOTE_BYTES_COST
);
assert_eq!(
conditions.execution_cost,
block_conds.execution_cost - QUOTE_EXECUTION_COST
);
assert_eq!(conditions.condition_cost, block_conds.condition_cost);
}
#[rstest]
#[case("bb13")]
#[case("e3c0")]
fn test_get_conditions_from_spendbundle_fast_forward(
#[case] filename: &str,
#[values(0, 1, 1_000_000, 5_000_000)] prev_tx_height: u32,
) {
let cost = 77_341_866;
let spend = CoinSpend::from_bytes(
&read(format!("../../ff-tests/{filename}.spend")).expect("read file"),
)
.expect("parse Spend");
let bundle = SpendBundle::new(vec![spend], Signature::default());
let mut a = make_allocator(LIMIT_HEAP);
let conditions =
get_conditions_from_spendbundle(&mut a, &bundle, cost, prev_tx_height, &TEST_CONSTANTS)
.expect("get_conditions_from_spendbundle");
assert_eq!(conditions.spends.len(), 1);
let spend = &conditions.spends[0];
assert_eq!(spend.flags, ELIGIBLE_FOR_FF | ELIGIBLE_FOR_DEDUP);
assert_eq!(conditions.cost, cost);
}
#[cfg(not(debug_assertions))]
fn convert_block_to_bundle(generator: &[u8], block_refs: &[Vec<u8>]) -> SpendBundle {
use crate::run_block_generator::setup_generator_args;
use chia_protocol::Bytes32;
use chia_protocol::Coin;
use chia_protocol::Program;
use clvm_traits::{FromClvm, destructure_tuple, match_tuple};
use clvm_utils::tree_hash_from_bytes;
use clvmr::NodePtr;
use clvmr::op_utils::first;
use clvmr::serde::node_from_bytes_backrefs;
let mut a = make_allocator(MEMPOOL_MODE);
let generator = node_from_bytes_backrefs(&mut a, generator).expect("node_from_bytes");
let args = setup_generator_args(&mut a, block_refs, 0).expect("setup_generator_args");
let dialect = ChiaDialect::new(MEMPOOL_MODE);
let Reduction(_, mut all_spends) =
run_program(&mut a, &dialect, generator, args, 11_000_000_000).expect("run_program");
all_spends = first(&a, all_spends).expect("first");
let mut spends = Vec::<CoinSpend>::new();
while let Some((spend, rest)) = a.next(all_spends) {
all_spends = rest;
let destructure_tuple!(parent_id, puzzle, amount, solution, _) =
<match_tuple!(Bytes32, Program, u64, Program, NodePtr)>::from_clvm(&a, spend)
.expect("parsing CLVM");
spends.push(CoinSpend::new(
Coin::new(
parent_id.try_into().expect("parent_id"),
tree_hash_from_bytes(puzzle.as_ref()).expect("hash").into(),
amount,
),
puzzle,
solution,
));
}
SpendBundle::new(spends, Signature::default())
}
#[cfg(not(debug_assertions))]
#[rstest]
#[case("new-agg-sigs")]
#[case("infinity-g1")]
#[case("block-1ee588dc")]
#[case("block-6fe59b24")]
#[case("block-b45268ac")]
#[case("block-c2a8df0d")]
#[case("block-e5002df2")]
#[case("block-4671894")]
#[case("block-225758")]
#[case("assert-puzzle-announce-fail")]
#[case("block-834752")]
#[case("block-834752-compressed")]
#[case("block-834760")]
#[case("block-834761")]
#[case("block-834765")]
#[case("block-834766")]
#[case("block-834768")]
#[case("create-coin-different-amounts")]
#[case("create-coin-hint-duplicate-outputs")]
#[case("create-coin-hint")]
#[case("create-coin-hint2")]
#[case("deep-recursion-plus")]
#[case("double-spend")]
#[case("duplicate-coin-announce")]
#[case("duplicate-create-coin")]
#[case("duplicate-height-absolute-div")]
#[case("duplicate-height-absolute-substr-tail")]
#[case("duplicate-height-absolute-substr")]
#[case("duplicate-height-absolute")]
#[case("duplicate-height-relative")]
#[case("duplicate-outputs")]
#[case("duplicate-reserve-fee")]
#[case("duplicate-seconds-absolute")]
#[case("duplicate-seconds-relative")]
#[case("height-absolute-ladder")]
#[case("invalid-conditions")]
#[case("just-puzzle-announce")]
#[case("many-create-coin")]
#[case("many-large-ints-negative")]
#[case("many-large-ints")]
#[case("max-height")]
#[case("multiple-reserve-fee")]
#[case("negative-reserve-fee")]
#[case("unknown-condition")]
#[case("duplicate-messages")]
fn run_generator(#[case] name: &str) {
use crate::test_generators::{print_conditions, print_diff};
use std::fs::read_to_string;
let filename = format!("../../generator-tests/{name}.txt");
println!("file: {filename}");
let test_file = read_to_string(filename).expect("test file not found");
let (generator, expected) = test_file.split_once('\n').expect("invalid test file");
let generator_buffer = hex::decode(generator).expect("invalid hex encoded generator");
let expected = match expected.split_once("STRICT:\n") {
Some((_, m)) => m,
None => expected,
};
let mut block_refs = Vec::<Vec<u8>>::new();
let filename = format!("../../generator-tests/{name}.env");
if let Ok(env_hex) = read_to_string(&filename) {
println!("block-ref file: {filename}");
block_refs.push(hex::decode(env_hex).expect("hex decode env-file"));
}
let bundle = convert_block_to_bundle(&generator_buffer, &block_refs);
let mut a2 = make_allocator(MEMPOOL_MODE);
let (execution_cost, block_cost, block_output) = {
let block_conds = run_block_generator2(
&mut a2,
&generator_buffer,
&block_refs,
11_000_000_000,
MEMPOOL_MODE | DONT_VALIDATE_SIGNATURE,
&Signature::default(),
None,
&TEST_CONSTANTS,
);
match block_conds {
Ok(ref conditions) => (
conditions.execution_cost,
conditions.cost,
print_conditions(&a2, &conditions, &a2),
),
Err(code) => {
println!("error: {code:?}");
(0, 0, format!("FAILED: {}\n", u32::from(code.1)))
}
}
};
let mut a1 = make_allocator(MEMPOOL_MODE);
let conds = get_conditions_from_spendbundle(
&mut a1,
&bundle,
11_000_000_000,
5_000_000,
&TEST_CONSTANTS,
);
let output = match conds {
Ok(mut conditions) => {
let block_byte_cost = generator_buffer.len() as u64 * TEST_CONSTANTS.cost_per_byte;
let program_spends = bundle.coin_spends.iter().map(|coin_spend| {
(
coin_spend.coin,
&coin_spend.puzzle_reveal,
&coin_spend.solution,
)
});
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} exe-cost: {execution_cost}"
);
println!("bundle_cost: {} bytes: {bundle_byte_cost}", conditions.cost);
println!("execution_cost: {}", conditions.execution_cost);
println!("condition_cost: {}", conditions.condition_cost);
assert!(conditions.cost - bundle_byte_cost <= block_cost - block_byte_cost);
assert!(conditions.cost > 0);
assert!(conditions.execution_cost > 0);
assert_eq!(
conditions.cost,
conditions.condition_cost + conditions.execution_cost + bundle_byte_cost
);
conditions.cost = block_cost;
conditions.execution_cost = execution_cost;
print_conditions(&a1, &conditions, &a2)
}
Err(code) => {
println!("error: {code:?}");
format!("FAILED: {}\n", u32::from(code.1))
}
};
if output != block_output {
print_diff(&output, &block_output);
panic!(
"run_block_generator2 produced a different result than get_conditions_from_spendbundle()"
);
}
if output != expected {
print_diff(&output, expected);
panic!("mismatching condition output");
}
}
}