Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extend the tests to cover the block-generator test cases #640

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion crates/chia-consensus/src/gen/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,4 @@ pub mod validation_error;
// unoptimized builds. Only run these with --release
#[cfg(not(debug_assertions))]
#[cfg(test)]
mod test_generators;
pub(crate) mod test_generators;
4 changes: 2 additions & 2 deletions crates/chia-consensus/src/gen/test_generators.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use text_diff::Difference;

use rstest::rstest;

fn print_conditions(a: &Allocator, c: &SpendBundleConditions) -> String {
pub(crate) fn print_conditions(a: &Allocator, c: &SpendBundleConditions) -> String {
let mut ret = String::new();
if c.reserve_fee > 0 {
ret += &format!("RESERVE_FEE: {}\n", c.reserve_fee);
Expand Down Expand Up @@ -115,7 +115,7 @@ fn print_conditions(a: &Allocator, c: &SpendBundleConditions) -> String {
ret
}

fn print_diff(output: &str, expected: &str) {
pub(crate) fn print_diff(output: &str, expected: &str) {
println!("\x1b[102m \x1b[0m - output from test");
println!("\x1b[101m \x1b[0m - expected output");
for diff in diff(expected, output, "\n").1 {
Expand Down
297 changes: 258 additions & 39 deletions crates/chia-consensus/src/spendbundle_conditions.rs
Original file line number Diff line number Diff line change
@@ -1,52 +1,49 @@
use crate::allocator::make_allocator;
use crate::consensus_constants::ConsensusConstants;
use crate::gen::conditions::{
process_single_spend, validate_conditions, MempoolVisitor, ParseState, SpendBundleConditions,
};
use crate::gen::flags::MEMPOOL_MODE;
use crate::gen::owned_conditions::OwnedSpendBundleConditions;
use crate::gen::run_block_generator::subtract_cost;
use crate::gen::validation_error::ValidationErr;
use crate::spendbundle_validation::get_flags_for_height_and_constants;
use chia_protocol::SpendBundle;
use clvm_utils::tree_hash;
use clvmr::allocator::Allocator;
use clvmr::chia_dialect::ChiaDialect;
use clvmr::chia_dialect::LIMIT_HEAP;
use clvmr::reduction::Reduction;
use clvmr::run_program::run_program;
use clvmr::serde::node_from_bytes;

pub fn get_conditions_from_spendbundle(
a: &mut Allocator,
spend_bundle: &SpendBundle,
max_cost: u64,
height: u32,
constants: &ConsensusConstants,
) -> Result<OwnedSpendBundleConditions, ValidationErr> {
) -> Result<SpendBundleConditions, ValidationErr> {
let flags = get_flags_for_height_and_constants(height, constants) | MEMPOOL_MODE;

// below is an adapted version of the code from run_block_generators::run_block_generator2()
// it assumes no block references are passed in
let mut cost_left = max_cost;
let dialect = ChiaDialect::new(flags);
let mut a: Allocator = make_allocator(LIMIT_HEAP);
let mut ret = SpendBundleConditions::default();
let mut state = ParseState::default();

for coin_spend in &spend_bundle.coin_spends {
// process the spend
let puz = node_from_bytes(&mut a, coin_spend.puzzle_reveal.as_slice())?;
let sol = node_from_bytes(&mut a, coin_spend.solution.as_slice())?;
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(&mut a, &dialect, puz, sol, cost_left)?;
let Reduction(clvm_cost, conditions) = run_program(a, &dialect, puz, sol, cost_left)?;

subtract_cost(&a, &mut cost_left, clvm_cost)?;
subtract_cost(a, &mut cost_left, clvm_cost)?;

let buf = tree_hash(&a, puz);
let buf = tree_hash(a, puz);
let puzzle_hash = a.new_atom(&buf)?;
process_single_spend::<MempoolVisitor>(
&a,
a,
&mut ret,
&mut state,
parent,
Expand All @@ -59,49 +56,271 @@ pub fn get_conditions_from_spendbundle(
)?;
}

validate_conditions(&a, &ret, state, a.nil(), flags)?;
validate_conditions(a, &ret, state, a.nil(), flags)?;
assert!(max_cost >= cost_left);
ret.cost = max_cost - cost_left;
let osbc = OwnedSpendBundleConditions::from(&a, ret);
Ok(osbc)
Ok(ret)
}

#[cfg(test)]
mod tests {
use crate::consensus_constants::TEST_CONSTANTS;

use super::*;
use crate::allocator::make_allocator;
use crate::gen::conditions::{ELIGIBLE_FOR_DEDUP, ELIGIBLE_FOR_FF};
use chia_bls::Signature;
use chia_protocol::{Coin, CoinSpend, Program};
use hex_literal::hex;

#[test]
fn test_get_conditions_from_spendbundle() {
let test_coin = Coin::new(
hex!("4444444444444444444444444444444444444444444444444444444444444444").into(),
hex!("3333333333333333333333333333333333333333333333333333333333333333").into(),
1,
);
use chia_protocol::CoinSpend;
use chia_traits::Streamable;
use clvmr::chia_dialect::LIMIT_HEAP;
use rstest::rstest;
use std::fs::read;

#[rstest]
#[case("3000253", 8, 2, 13_344_870)]
#[case("1000101", 34, 15, 66_723_677)]
fn test_get_conditions_from_spendbundle(
#[case] filename: &str,
#[case] spends: usize,
#[case] additions: usize,
#[values(0, 1, 1000000, 5000000)] 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, 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);
}

#[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)] height: u32,
) {
let cost = 2_125_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, 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))]
use crate::gen::flags::{ALLOW_BACKREFS, ENABLE_MESSAGE_CONDITIONS};

#[cfg(not(debug_assertions))]
const DEFAULT_FLAGS: u32 = ALLOW_BACKREFS | ENABLE_MESSAGE_CONDITIONS | MEMPOOL_MODE;

// given a block generator and block-refs, convert run the generator to
// produce the SpendBundle for the block without runningi, or validating,
// the puzzles.
#[cfg(not(debug_assertions))]
fn convert_block_to_bundle(generator: &[u8], block_refs: &[Vec<u8>]) -> SpendBundle {
use crate::gen::run_block_generator::extract_n;
use crate::gen::run_block_generator::setup_generator_args;
use crate::gen::validation_error::ErrorCode;
use chia_protocol::Coin;
use clvmr::op_utils::first;
use clvmr::serde::node_from_bytes_backrefs;
use clvmr::serde::node_to_bytes;

let mut a = make_allocator(DEFAULT_FLAGS);

let generator = node_from_bytes_backrefs(&mut a, generator).expect("node_from_bytes");
let args = setup_generator_args(&mut a, block_refs).expect("setup_generator_args");
let dialect = ChiaDialect::new(DEFAULT_FLAGS);
let Reduction(_, mut all_spends) =
run_program(&mut a, &dialect, generator, args, 11_000_000_000).expect("run_program");

let solution = hex!("ffff31ffb0997cc43ed8788f841fcf3071f6f212b89ba494b6ebaf1bda88c3f9de9d968a61f3b7284a5ee13889399ca71a026549a2ff8568656c6c6f8080").to_vec();
// ((49 0x997cc43ed8788f841fcf3071f6f212b89ba494b6ebaf1bda88c3f9de9d968a61f3b7284a5ee13889399ca71a026549a2 "hello"))
all_spends = first(&a, all_spends).expect("first");

let spend = CoinSpend::new(test_coin, Program::new(vec![1_u8].into()), solution.into());
let mut spends = Vec::<CoinSpend>::new();

let coin_spends: Vec<CoinSpend> = vec![spend];
let spend_bundle = SpendBundle {
coin_spends,
aggregated_signature: Signature::default(),
// at this point all_spends is a list of:
// (parent-coin-id puzzle-reveal amount solution . extra)
// where extra may be nil, or additional extension data
while let Some((spend, rest)) = a.next(all_spends) {
all_spends = rest;
// process the spend
let [parent_id, puzzle, amount, solution, _spend_level_extra] =
extract_n::<5>(&a, spend, ErrorCode::InvalidCondition).expect("extract_n");

spends.push(CoinSpend::new(
Coin::new(
a.atom(parent_id).as_ref().try_into().expect("parent_id"),
tree_hash(&a, puzzle).into(),
a.number(amount).try_into().expect("amount"),
),
node_to_bytes(&a, puzzle).expect("node_to_bytes").into(),
node_to_bytes(&a, solution).expect("node_to_bytes").into(),
));
}
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("infinite-recursion1")]
//#[case("infinite-recursion2")]
//#[case("infinite-recursion3")]
//#[case("infinite-recursion4")]
#[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("recursion-pairs")]
#[case("unknown-condition")]
#[case("duplicate-messages")]
fn run_generator(#[case] name: &str) {
use crate::gen::run_block_generator::run_block_generator;
use crate::gen::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);

// run the whole block through run_block_generator() to ensure the
// output conditions match and update the cost. The cost
// of just the spend bundle will be lower
let (block_cost, block_output) = {
let mut a = make_allocator(DEFAULT_FLAGS);
let block_conds = run_block_generator::<_, MempoolVisitor, _>(
&mut a,
&generator_buffer,
&block_refs,
11_000_000_000,
DEFAULT_FLAGS,
&TEST_CONSTANTS,
);
match block_conds {
Ok(ref conditions) => (conditions.cost, print_conditions(&a, &conditions)),
Err(code) => {
println!("error: {code:?}");
(0, format!("FAILED: {}\n", u32::from(code.1)))
}
}
};
let osbc = get_conditions_from_spendbundle(
&spend_bundle,
TEST_CONSTANTS.max_block_cost_clvm,
236,

let mut a = make_allocator(LIMIT_HEAP);
let conds = get_conditions_from_spendbundle(
&mut a,
&bundle,
11_000_000_000,
5_000_000,
&TEST_CONSTANTS,
)
.expect("test should pass");
);

let output = match conds {
Ok(mut conditions) => {
// the cost of running the spend bundle should never be higher
// than the whole block but it's likely less.
println!("block_cost: {block_cost}");
println!("bundle_cost: {}", conditions.cost);
assert!(conditions.cost <= block_cost);
assert!(conditions.cost > 0);
// update the cost we print here, just to be compatible with
// the test cases we have. We've already ensured the cost is
// lower
conditions.cost = block_cost;
print_conditions(&a, &conditions)
}
Err(code) => {
println!("error: {code:?}");
format!("FAILED: {}\n", u32::from(code.1))
}
};

if output != block_output {
print_diff(&output, &block_output);
panic!("run_block_generator produced a different result than get_conditions_from_spendbundle()");
}

assert!(osbc.spends.len() == 1);
assert!(osbc.agg_sig_unsafe.len() == 1);
if output != expected {
print_diff(&output, expected);
panic!("mismatching condition output");
}
}
}
Loading
Loading