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

Add functions get_flags_for_height_and_constants() and get_conditions_from_spendbundle() #634

Merged
merged 15 commits into from
Aug 6, 2024
Merged
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;
6 changes: 5 additions & 1 deletion crates/chia-consensus/src/gen/run_block_generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ use clvmr::run_program::run_program;
use clvmr::serde::{node_from_bytes, node_from_bytes_backrefs, node_from_bytes_backrefs_record};
use std::collections::{HashMap, HashSet};

fn subtract_cost(a: &Allocator, cost_left: &mut Cost, subtract: Cost) -> Result<(), ValidationErr> {
pub fn subtract_cost(
a: &Allocator,
cost_left: &mut Cost,
subtract: Cost,
) -> Result<(), ValidationErr> {
if subtract > *cost_left {
Err(ValidationErr(a.nil(), ErrorCode::CostExceeded))
} else {
Expand Down
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
2 changes: 2 additions & 0 deletions crates/chia-consensus/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@ pub mod gen;
pub mod generator_rom;
pub mod merkle_set;
pub mod merkle_tree;
pub mod spendbundle_conditions;
pub mod spendbundle_validation;
326 changes: 326 additions & 0 deletions crates/chia-consensus/src/spendbundle_conditions.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,326 @@
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::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::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,
Rigidity marked this conversation as resolved.
Show resolved Hide resolved
max_cost: u64,
height: u32,
constants: &ConsensusConstants,
) -> 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 ret = SpendBundleConditions::default();
let mut state = ParseState::default();

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())?;
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)?;

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

let buf = tree_hash(a, puz);
let puzzle_hash = a.new_atom(&buf)?;
process_single_spend::<MempoolVisitor>(
a,
&mut ret,
&mut state,
parent,
puzzle_hash,
amount,
conditions,
flags,
&mut cost_left,
constants,
)?;
}

validate_conditions(a, &ret, state, a.nil(), flags)?;
assert!(max_cost >= cost_left);
ret.cost = max_cost - cost_left;
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::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");

all_spends = first(&a, all_spends).expect("first");

let mut spends = Vec::<CoinSpend>::new();

// 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 mut a = make_allocator(LIMIT_HEAP);
let conds = get_conditions_from_spendbundle(
&mut a,
&bundle,
11_000_000_000,
5_000_000,
&TEST_CONSTANTS,
);

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()");
}

if output != expected {
print_diff(&output, expected);
panic!("mismatching condition output");
}
}
}
Loading
Loading