From 135f2326def2f48d055c3f259ab614baad7bcf8d Mon Sep 17 00:00:00 2001 From: arvidn Date: Sat, 23 Sep 2023 16:33:33 +0200 Subject: [PATCH 1/2] introduce ELIGIBLE_FOR_FF flag on Spends when parsing and validating conditions. This indicates whether some of the requirements are met to be able to fast-forward a singleton spend --- chia-tools/src/bin/analyze-chain.rs | 11 +- chia-tools/src/bin/test-block-generators.rs | 31 ++- fuzz/fuzz_targets/fast-forward.rs | 3 + fuzz/fuzz_targets/parse-conditions.rs | 5 +- fuzz/fuzz_targets/parse-spends.rs | 10 +- fuzz/fuzz_targets/process-spend.rs | 5 +- fuzz/fuzz_targets/run-generator.rs | 19 +- fuzz/fuzz_targets/run-puzzle.rs | 2 + src/fast_forward.rs | 3 + src/gen/conditions.rs | 290 ++++++++++++++++++-- src/gen/flags.rs | 7 +- src/gen/mod.rs | 1 + src/gen/policy.rs | 11 + src/gen/run_block_generator.rs | 10 +- src/gen/run_puzzle.rs | 10 +- src/gen/test_generators.rs | 62 +++-- wheel/src/api.rs | 20 +- wheel/src/run_generator.rs | 47 +++- 18 files changed, 464 insertions(+), 83 deletions(-) create mode 100644 src/gen/policy.rs diff --git a/chia-tools/src/bin/analyze-chain.rs b/chia-tools/src/bin/analyze-chain.rs index de77830a9..8e6a82778 100644 --- a/chia-tools/src/bin/analyze-chain.rs +++ b/chia-tools/src/bin/analyze-chain.rs @@ -7,7 +7,7 @@ use std::time::SystemTime; use sqlite::State; -use chia::gen::conditions::parse_spends; +use chia::gen::conditions::{parse_spends, MempoolPolicy}; use chia::gen::flags::MEMPOOL_MODE; use chia::gen::validation_error::ValidationErr; use chia::generator_rom::{COST_PER_BYTE, GENERATOR_ROM}; @@ -183,8 +183,13 @@ fn main() { let start_conditions = SystemTime::now(); // we pass in what's left of max_cost here, to fail early in case the // cost of a condition brings us over the cost limit - let conds = match parse_spends(&a, generator_output, ti.cost - clvm_cost, MEMPOOL_MODE) - { + let conds = match parse_spends( + &a, + generator_output, + ti.cost - clvm_cost, + MEMPOOL_MODE, + &mut MempoolPolicy::default(), + ) { Err(ValidationErr(_, _)) => { panic!("failed to parse conditions in block {height}"); } diff --git a/chia-tools/src/bin/test-block-generators.rs b/chia-tools/src/bin/test-block-generators.rs index 3b33f0005..225594602 100644 --- a/chia-tools/src/bin/test-block-generators.rs +++ b/chia-tools/src/bin/test-block-generators.rs @@ -5,9 +5,7 @@ use chia_traits::Streamable; use sqlite::State; -use chia::gen::conditions::NewCoin; -use chia::gen::conditions::Spend; -use chia::gen::conditions::SpendBundleConditions; +use chia::gen::conditions::{NewCoin, NonePolicy, Spend, SpendBundleConditions}; use chia::gen::flags::{ALLOW_BACKREFS, MEMPOOL_MODE}; use chia::gen::run_block_generator::{run_block_generator, run_block_generator2}; use clvmr::allocator::NodePtr; @@ -159,9 +157,9 @@ fn main() { }; let block_runner = if args.rust_generator { - run_block_generator2 + run_block_generator2::<_, NonePolicy> } else { - run_block_generator + run_block_generator::<_, NonePolicy> }; while let Ok(State::Row) = statement.next() { @@ -241,8 +239,15 @@ fn main() { prg.as_ref() }; - let mut conditions = block_runner(&mut a, generator, &block_refs, ti.cost, flags) - .expect("failed to run block generator"); + let mut conditions = block_runner( + &mut a, + generator, + &block_refs, + ti.cost, + flags, + &mut NonePolicy::default(), + ) + .expect("failed to run block generator"); if args.rust_generator || args.test_backrefs { assert!(conditions.cost <= ti.cost); @@ -257,9 +262,15 @@ fn main() { } if args.validate { - let mut baseline = - run_block_generator(&mut a, prg.as_ref(), &block_refs, ti.cost, 0) - .expect("failed to run block generator"); + let mut baseline = run_block_generator( + &mut a, + prg.as_ref(), + &block_refs, + ti.cost, + 0, + &mut NonePolicy::default(), + ) + .expect("failed to run block generator"); assert_eq!(baseline.cost, ti.cost); baseline.spends.sort_by_key(|s| *s.coin_id); diff --git a/fuzz/fuzz_targets/fast-forward.rs b/fuzz/fuzz_targets/fast-forward.rs index d7dbe9816..de8360385 100644 --- a/fuzz/fuzz_targets/fast-forward.rs +++ b/fuzz/fuzz_targets/fast-forward.rs @@ -1,5 +1,6 @@ #![no_main] use chia::fast_forward::fast_forward_singleton; +use chia::gen::conditions::MempoolPolicy; use chia::gen::run_puzzle::run_puzzle; use chia::gen::validation_error::ValidationErr; use chia_protocol::Bytes32; @@ -64,6 +65,7 @@ fuzz_target!(|data: &[u8]| { spend.coin.amount, 11000000000, 0, + &mut MempoolPolicy::default(), ); // run new spend @@ -75,6 +77,7 @@ fuzz_target!(|data: &[u8]| { new_coin.amount, 11000000000, 0, + &mut MempoolPolicy::default(), ); match (conditions1, conditions2) { diff --git a/fuzz/fuzz_targets/parse-conditions.rs b/fuzz/fuzz_targets/parse-conditions.rs index dc7a3a2f6..e8c1875da 100644 --- a/fuzz/fuzz_targets/parse-conditions.rs +++ b/fuzz/fuzz_targets/parse-conditions.rs @@ -1,7 +1,9 @@ #![no_main] use libfuzzer_sys::fuzz_target; -use chia::gen::conditions::{parse_conditions, ParseState, Spend, SpendBundleConditions}; +use chia::gen::conditions::{ + parse_conditions, MempoolPolicy, ParseState, Spend, SpendBundleConditions, +}; use chia_protocol::Bytes32; use chia_protocol::Coin; use clvm_utils::tree_hash; @@ -72,6 +74,7 @@ fuzz_target!(|data: &[u8]| { input, *flags, &mut max_cost, + &mut MempoolPolicy::default(), ); } }); diff --git a/fuzz/fuzz_targets/parse-spends.rs b/fuzz/fuzz_targets/parse-spends.rs index 2fa45c252..c0b1378d6 100644 --- a/fuzz/fuzz_targets/parse-spends.rs +++ b/fuzz/fuzz_targets/parse-spends.rs @@ -1,7 +1,7 @@ #![no_main] use libfuzzer_sys::fuzz_target; -use chia::gen::conditions::parse_spends; +use chia::gen::conditions::{parse_spends, MempoolPolicy}; use clvmr::allocator::Allocator; use fuzzing_utils::{make_tree, BitCursor}; @@ -11,6 +11,12 @@ fuzz_target!(|data: &[u8]| { let mut a = Allocator::new(); let input = make_tree(&mut a, &mut BitCursor::new(data), false); for flags in &[0, COND_ARGS_NIL, STRICT_ARGS_COUNT, NO_UNKNOWN_CONDS] { - let _ret = parse_spends(&a, input, 33000000000, *flags); + let _ret = parse_spends( + &a, + input, + 33000000000, + *flags, + &mut MempoolPolicy::default(), + ); } }); diff --git a/fuzz/fuzz_targets/process-spend.rs b/fuzz/fuzz_targets/process-spend.rs index 6db228f2d..1e43222d3 100644 --- a/fuzz/fuzz_targets/process-spend.rs +++ b/fuzz/fuzz_targets/process-spend.rs @@ -1,5 +1,7 @@ #![no_main] -use chia::gen::conditions::{process_single_spend, ParseState, SpendBundleConditions}; +use chia::gen::conditions::{ + process_single_spend, MempoolPolicy, ParseState, SpendBundleConditions, +}; use chia::gen::flags::{COND_ARGS_NIL, NO_UNKNOWN_CONDS, STRICT_ARGS_COUNT}; use clvmr::allocator::Allocator; use fuzzing_utils::{make_tree, BitCursor}; @@ -28,6 +30,7 @@ fuzz_target!(|data: &[u8]| { conds, *flags, &mut cost_left, + &mut MempoolPolicy::default(), ); } }); diff --git a/fuzz/fuzz_targets/run-generator.rs b/fuzz/fuzz_targets/run-generator.rs index da1f70c78..f23b3ade8 100644 --- a/fuzz/fuzz_targets/run-generator.rs +++ b/fuzz/fuzz_targets/run-generator.rs @@ -1,5 +1,6 @@ #![no_main] use chia::allocator::make_allocator; +use chia::gen::conditions::MempoolPolicy; use chia::gen::flags::{ALLOW_BACKREFS, LIMIT_OBJECTS}; use chia::gen::run_block_generator::{run_block_generator, run_block_generator2}; use chia::gen::validation_error::{ErrorCode, ValidationErr}; @@ -8,11 +9,25 @@ use libfuzzer_sys::fuzz_target; fuzz_target!(|data: &[u8]| { let mut a1 = make_allocator(LIMIT_HEAP | LIMIT_OBJECTS); - let r1 = run_block_generator::<&[u8]>(&mut a1, data, &[], 110000000, ALLOW_BACKREFS); + let r1 = run_block_generator::<&[u8], MempoolPolicy>( + &mut a1, + data, + &[], + 110000000, + ALLOW_BACKREFS, + &mut MempoolPolicy::default(), + ); drop(a1); let mut a2 = make_allocator(LIMIT_HEAP | LIMIT_OBJECTS); - let r2 = run_block_generator2::<&[u8]>(&mut a2, data, &[], 110000000, ALLOW_BACKREFS); + let r2 = run_block_generator2::<&[u8], MempoolPolicy>( + &mut a2, + data, + &[], + 110000000, + ALLOW_BACKREFS, + &mut MempoolPolicy::default(), + ); drop(a2); match (r1, r2) { diff --git a/fuzz/fuzz_targets/run-puzzle.rs b/fuzz/fuzz_targets/run-puzzle.rs index 59ce71043..75b3ee67c 100644 --- a/fuzz/fuzz_targets/run-puzzle.rs +++ b/fuzz/fuzz_targets/run-puzzle.rs @@ -1,4 +1,5 @@ #![no_main] +use chia::gen::conditions::MempoolPolicy; use chia::gen::flags::ALLOW_BACKREFS; use chia::gen::run_puzzle::run_puzzle; use chia_protocol::CoinSpend; @@ -21,5 +22,6 @@ fuzz_target!(|data: &[u8]| { spend.coin.amount, 11000000000, ALLOW_BACKREFS, + &mut MempoolPolicy::default(), ); }); diff --git a/src/fast_forward.rs b/src/fast_forward.rs index 3efe6d980..3b22268cf 100644 --- a/src/fast_forward.rs +++ b/src/fast_forward.rs @@ -182,6 +182,7 @@ pub fn fast_forward_singleton( #[cfg(test)] mod tests { use super::*; + use crate::gen::conditions::MempoolPolicy; use crate::gen::run_puzzle::run_puzzle; use chia_protocol::CoinSpend; use chia_traits::streamable::Streamable; @@ -249,6 +250,7 @@ mod tests { spend.coin.amount, 11000000000, 0, + &mut MempoolPolicy::default(), ) .expect("run_puzzle"); @@ -261,6 +263,7 @@ mod tests { new_coin.amount, 11000000000, 0, + &mut MempoolPolicy::default(), ) .expect("run_puzzle"); diff --git a/src/gen/conditions.rs b/src/gen/conditions.rs index 4bdc6ecb6..14c39bb6f 100644 --- a/src/gen/conditions.rs +++ b/src/gen/conditions.rs @@ -18,6 +18,7 @@ use crate::gen::flags::{ AGG_SIG_ARGS, COND_ARGS_NIL, LIMIT_ANNOUNCES, NO_RELATIVE_CONDITIONS_ON_EPHEMERAL, NO_UNKNOWN_CONDS, STRICT_ARGS_COUNT, }; +use crate::gen::policy::ConditionPolicy; use crate::gen::validation_error::check_nil; use chia_protocol::bytes::Bytes32; use clvmr::allocator::{Allocator, NodePtr, SExp}; @@ -28,6 +29,114 @@ use std::collections::{HashMap, HashSet}; use std::hash::{Hash, Hasher}; use std::sync::Arc; +// spend flags + +// a spend is eligible for deduplication if it does not have any AGG_SIG_ME +// nor AGG_SIG_UNSAFE +pub const ELIGIBLE_FOR_DEDUP: u32 = 1; + +// If the spend bundle contained *any* relative seconds or height condition, this flag is set +pub const HAS_RELATIVE_CONDITION: u32 = 2; + +// If the CoinSpend is eligible for fast-forward, this flag is set. A spend is +// eligible if: +// 1. the input coin amount is odd +// 2. There are no AGG_SIG_ME, AGG_SIG_PARENT, AGG_SIG_PARENT_* conditions +// 3. No ASSERT_MY_COIN_ID condition, no more than one ASSERT_MY_PARENT_ID condition +// (as the second condition) +// 4. it has an output coin with the same puzzle hash and amount as the spend itself +pub const ELIGIBLE_FOR_FF: u32 = 4; + +#[derive(Default)] +pub struct NonePolicy {} + +impl ConditionPolicy for NonePolicy { + fn new_spend(&mut self, _spend: &mut Spend) {} + fn condition(&mut self, _spend: &mut Spend, _c: &Condition) {} + fn post_spend(&mut self, _a: &Allocator, _spend: &mut Spend) {} +} + +#[derive(Default)] +pub struct MempoolPolicy { + condition_counter: i32, +} + +impl ConditionPolicy for MempoolPolicy { + fn new_spend(&mut self, spend: &mut Spend) { + // assume it's eligibe. We'll clear this flag if it isn't + let mut spend_flags = ELIGIBLE_FOR_DEDUP; + + // spend eligible for fast-forward must be singletons, which use odd amounts + if (spend.coin_amount & 1) == 1 { + spend_flags |= ELIGIBLE_FOR_FF; + } + spend.flags |= spend_flags; + self.condition_counter = 0; + } + + fn condition(&mut self, spend: &mut Spend, c: &Condition) { + match c { + Condition::AssertMyCoinId(_) => { + spend.flags &= !ELIGIBLE_FOR_FF; + } + Condition::AssertMyParentId(_) => { + // the singleton_top_layer_v1_1.clsp will only emit two + // conditions, ASSERT_MY_AMOUNT and ASSERT_MY_PARENT_ID (in that + // order). So we expect this conditon as the second in the list. + // Any other conditions of this kind have to have been produced + // by the inner puzzle, which we don't have control over. So in + // that case this spend is not eligible for fast-forward. + if self.condition_counter != 1 { + spend.flags &= !ELIGIBLE_FOR_FF; + } + } + Condition::AggSigMe(_, _) => { + spend.flags &= !ELIGIBLE_FOR_DEDUP; + spend.flags &= !ELIGIBLE_FOR_FF; + } + Condition::AggSigParent(_, _) => { + spend.flags &= !ELIGIBLE_FOR_DEDUP; + spend.flags &= !ELIGIBLE_FOR_FF; + } + Condition::AggSigPuzzle(_, _) => { + spend.flags &= !ELIGIBLE_FOR_DEDUP; + } + Condition::AggSigAmount(_, _) => { + spend.flags &= !ELIGIBLE_FOR_DEDUP; + } + Condition::AggSigPuzzleAmount(_, _) => { + spend.flags &= !ELIGIBLE_FOR_DEDUP; + } + Condition::AggSigParentAmount(_, _) => { + spend.flags &= !ELIGIBLE_FOR_DEDUP; + spend.flags &= !ELIGIBLE_FOR_FF; + } + Condition::AggSigParentPuzzle(_, _) => { + spend.flags &= !ELIGIBLE_FOR_DEDUP; + spend.flags &= !ELIGIBLE_FOR_FF; + } + Condition::AggSigUnsafe(_, _) => { + spend.flags &= !ELIGIBLE_FOR_DEDUP; + } + _ => {} + } + self.condition_counter += 1; + } + + fn post_spend(&mut self, a: &Allocator, spend: &mut Spend) { + // if this still looks like it might be a singleton, check the output coins + // to look for something that looks like a singleton output, with the same + // puzzle hash as our input coin + if (spend.flags & ELIGIBLE_FOR_FF) != 0 + && !spend.create_coin.iter().any(|c| { + c.amount == spend.coin_amount && a.atom(spend.puzzle_hash) == c.puzzle_hash + }) + { + spend.flags &= !ELIGIBLE_FOR_FF; + } + } +} + // The structure of conditions, returned from a generator program, is a list, // where the first element is used, and any additional elements are left unused, // for future soft-forks. @@ -465,15 +574,6 @@ impl PartialEq for NewCoin { } } -// spend flags - -// a spend is eligible for deduplication if it does not have any AGG_SIG_ME -// nor AGG_SIG_UNSAFE -pub const ELIGIBLE_FOR_DEDUP: u32 = 1; - -// If the spend bundle contained *any* relative seconds or height condition, this flag is set -pub const HAS_RELATIVE_CONDITION: u32 = 2; - // These are all the conditions related directly to a specific spend. #[derive(Debug, Clone)] pub struct Spend { @@ -544,8 +644,7 @@ impl Spend { agg_sig_puzzle_amount: Vec::new(), agg_sig_parent_amount: Vec::new(), agg_sig_parent_puzzle: Vec::new(), - // assume it's eligible until we see an agg-sig condition - flags: ELIGIBLE_FOR_DEDUP, + flags: 0, } } } @@ -655,7 +754,7 @@ pub(crate) fn parse_single_spend( } #[allow(clippy::too_many_arguments)] -pub fn process_single_spend( +pub fn process_single_spend( a: &Allocator, ret: &mut SpendBundleConditions, state: &mut ParseState, @@ -665,6 +764,7 @@ pub fn process_single_spend( conditions: NodePtr, flags: u32, max_cost: &mut Cost, + policy: &mut T, ) -> Result<(), ValidationErr> { let parent_id = sanitize_hash(a, parent_id, 32, ErrorCode::InvalidParentId)?; let puzzle_hash = sanitize_hash(a, puzzle_hash, 32, ErrorCode::InvalidPuzzleHash)?; @@ -687,9 +787,11 @@ pub fn process_single_spend( ret.removal_amount += my_amount as u128; - let coin_spend = Spend::new(parent_id, my_amount, puzzle_hash, coin_id); + let mut spend = Spend::new(parent_id, my_amount, puzzle_hash, coin_id); + + policy.new_spend(&mut spend); - parse_conditions(a, ret, state, coin_spend, conditions, flags, max_cost) + parse_conditions(a, ret, state, spend, conditions, flags, max_cost, policy) } fn assert_not_ephemeral(spend_flags: &mut u32, state: &mut ParseState, idx: usize) { @@ -710,7 +812,8 @@ fn decrement(cnt: &mut u32, n: NodePtr) -> Result<(), ValidationErr> { } } -pub fn parse_conditions( +#[allow(clippy::too_many_arguments)] +pub fn parse_conditions( a: &Allocator, ret: &mut SpendBundleConditions, state: &mut ParseState, @@ -718,6 +821,7 @@ pub fn parse_conditions( mut iter: NodePtr, flags: u32, max_cost: &mut Cost, + policy: &mut T, ) -> Result<(), ValidationErr> { let mut announce_countdown: u32 = if (flags & LIMIT_ANNOUNCES) != 0 { 1024 @@ -765,6 +869,7 @@ pub fn parse_conditions( } c = rest(a, c)?; let cva = parse_args(a, c, op, flags)?; + policy.condition(&mut spend, &cva); match cva { Condition::ReserveFee(limit) => { // reserve fees are accumulated @@ -957,35 +1062,27 @@ pub fn parse_conditions( } Condition::AggSigMe(pk, msg) => { spend.agg_sig_me.push((pk, msg)); - spend.flags &= !ELIGIBLE_FOR_DEDUP; } Condition::AggSigParent(pk, msg) => { spend.agg_sig_parent.push((pk, msg)); - spend.flags &= !ELIGIBLE_FOR_DEDUP; } Condition::AggSigPuzzle(pk, msg) => { spend.agg_sig_puzzle.push((pk, msg)); - spend.flags &= !ELIGIBLE_FOR_DEDUP; } Condition::AggSigAmount(pk, msg) => { spend.agg_sig_amount.push((pk, msg)); - spend.flags &= !ELIGIBLE_FOR_DEDUP; } Condition::AggSigPuzzleAmount(pk, msg) => { spend.agg_sig_puzzle_amount.push((pk, msg)); - spend.flags &= !ELIGIBLE_FOR_DEDUP; } Condition::AggSigParentAmount(pk, msg) => { spend.agg_sig_parent_amount.push((pk, msg)); - spend.flags &= !ELIGIBLE_FOR_DEDUP; } Condition::AggSigParentPuzzle(pk, msg) => { spend.agg_sig_parent_puzzle.push((pk, msg)); - spend.flags &= !ELIGIBLE_FOR_DEDUP; } Condition::AggSigUnsafe(pk, msg) => { ret.agg_sig_unsafe.push((pk, msg)); - spend.flags &= !ELIGIBLE_FOR_DEDUP; } Condition::Softfork(cost) => { if *max_cost < cost { @@ -1000,6 +1097,8 @@ pub fn parse_conditions( } } + policy.post_spend(a, &mut spend); + ret.spends.push(spend); Ok(()) } @@ -1031,11 +1130,12 @@ fn is_ephemeral( // This function parses, and validates aspects of, the above structure and // returns a list of all spends, along with all conditions, organized by // condition op-code -pub fn parse_spends( +pub fn parse_spends( a: &Allocator, spends: NodePtr, max_cost: Cost, flags: u32, + policy: &mut T, ) -> Result { let mut ret = SpendBundleConditions::default(); let mut state = ParseState::default(); @@ -1062,6 +1162,7 @@ pub fn parse_spends( conds, flags, &mut cost_left, + policy, )?; } @@ -1466,7 +1567,7 @@ fn cond_test_cb( print!("{:02x}", c); } println!(); - match parse_spends(&a, n, 11000000000, flags) { + match parse_spends(&a, n, 11000000000, flags, &mut MempoolPolicy::default()) { Ok(list) => { for n in &list.spends { println!("{:?}", n); @@ -2366,7 +2467,7 @@ fn test_create_coin_max_amount() { assert_eq!(c.amount, 0xffffffffffffffff_u64); assert_eq!(c.hint, a.null()); } - assert_eq!(spend.flags, ELIGIBLE_FOR_DEDUP); + assert_eq!(spend.flags, ELIGIBLE_FOR_DEDUP | ELIGIBLE_FOR_FF); } #[test] @@ -4214,3 +4315,140 @@ fn test_limit_announcements( r.unwrap(); } } + +#[test] +fn test_eligible_for_ff_assert_parent() { + // this is a model example of a spend that's eligible for FF + // it mimics the output of singleton_top_layer_v1_1 + // the ASSERT_MY_PARENT_ID is only allowed as the second condition + // 73=ASSERT_MY_AMOUNT + // 71=ASSERT_MY_PARENT_ID + // 51=CREATE_COIN + let test: &str = &format!( + "(\ + (({{h1}} ({{h2}} (123 (\ + ((73 (123 ) \ + ((71 ({{h1}} ) \ + ((51 ({{h2}} (123 ) \ + ))\ + ))" + ); + + let (_a, cond) = cond_test(test).expect("cond_test"); + assert!(cond.spends.len() == 1); + assert!((cond.spends[0].flags & ELIGIBLE_FOR_FF) != 0); +} + +#[test] +fn test_eligible_for_ff_even_amount() { + // coins with even amounts cannot be singletons, even if all other + // conditions are met + // 73=ASSERT_MY_AMOUNT + // 71=ASSERT_MY_PARENT_ID + // 51=CREATE_COIN + let test: &str = &format!( + "(\ + (({{h1}} ({{h2}} (122 (\ + ((73 (122 ) \ + ((71 ({{h1}} ) \ + ((51 ({{h2}} (122 ) \ + ))\ + ))" + ); + + let (_a, cond) = cond_test(test).expect("cond_test"); + assert!(cond.spends.len() == 1); + assert!((cond.spends[0].flags & ELIGIBLE_FOR_FF) == 0); +} + +#[cfg(test)] +#[rstest] +#[case(123, "{h2}", true)] +#[case(122, "{h1}", false)] +#[case(1, "{h1}", false)] +#[case(123, "{h1}", false)] +fn test_eligible_for_ff_output_coin(#[case] amount: u64, #[case] ph: &str, #[case] eligible: bool) { + // in order to be elgibible for fast forward, there needs to be an output + // coin with the same amount and same puzzle hash + // 51=CREATE_COIN + let test: &str = &format!( + "(\ + (({{h1}} ({{h2}} (123 (\ + ((51 ({} ({} ) \ + ))\ + ))", + ph, amount + ); + + let (_a, cond) = cond_test(test).expect("cond_test"); + assert!(cond.spends.len() == 1); + let flags = cond.spends[0].flags; + if eligible { + assert!((flags & ELIGIBLE_FOR_FF) != 0); + } else { + assert!((flags & ELIGIBLE_FOR_FF) == 0); + } +} + +#[cfg(test)] +#[rstest] +#[case(ASSERT_MY_PARENT_ID, "{h1}")] +#[case(ASSERT_MY_COIN_ID, "{coin12}")] +fn test_eligible_for_ff_invalid_assert_parent( + #[case] condition: ConditionOpcode, + #[case] arg: &str, +) { + // the ASSERT_MY_PARENT_ID is only allowed as the second condition + // and ASSERT_MY_COIN_ID is disallowed + // 73=ASSERT_MY_AMOUNT + // 51=CREATE_COIN + let test: &str = &format!( + "(\ + (({{h1}} ({{h2}} (123 (\ + (({} ({} ) \ + ((73 (123 ) \ + ((51 ({{h2}} (123 ) \ + ))\ + ))", + condition, arg + ); + + let (_a, cond) = cond_test(test).expect("cond_test"); + assert!(cond.spends.len() == 1); + assert!((cond.spends[0].flags & ELIGIBLE_FOR_FF) == 0); +} + +#[cfg(test)] +#[rstest] +#[case(AGG_SIG_ME, false)] +#[case(AGG_SIG_PARENT, false)] +#[case(AGG_SIG_PARENT_AMOUNT, false)] +#[case(AGG_SIG_PARENT_PUZZLE, false)] +#[case(AGG_SIG_UNSAFE, true)] +#[case(AGG_SIG_PUZZLE, true)] +#[case(AGG_SIG_AMOUNT, true)] +#[case(AGG_SIG_PUZZLE_AMOUNT, true)] +fn test_eligible_for_ff_invalid_agg_sig_me( + #[case] condition: ConditionOpcode, + #[case] eligible: bool, +) { + // 51=CREATE_COIN + let test: &str = &format!( + "(\ + (({{h1}} ({{h2}} (1 (\ + (({} ({{pubkey}} ({{msg1}} ) \ + ((51 ({{h2}} (1 ) \ + ))\ + ))", + condition + ); + + let (_a, cond) = cond_test_flag(test, ENABLE_SOFTFORK_CONDITION).expect("cond_test"); + assert!(cond.spends.len() == 1); + let flags = cond.spends[0].flags; + if eligible { + assert!((flags & ELIGIBLE_FOR_FF) != 0); + } else { + assert!((flags & ELIGIBLE_FOR_FF) == 0); + } +} diff --git a/src/gen/flags.rs b/src/gen/flags.rs index 1ae72bf06..f46c030d5 100644 --- a/src/gen/flags.rs +++ b/src/gen/flags.rs @@ -45,9 +45,14 @@ pub const LIMIT_ANNOUNCES: u32 = 0x1000000; // contain back-references pub const ALLOW_BACKREFS: u32 = 0x2000000; +// When set, the "flags" field of the Spend objects will be set depending on +// what features are detected of the spends +pub const ANALYZE_SPENDS: u32 = 0x4000000; + pub const MEMPOOL_MODE: u32 = CLVM_MEMPOOL_MODE | NO_UNKNOWN_CONDS | COND_ARGS_NIL | STRICT_ARGS_COUNT | NO_RELATIVE_CONDITIONS_ON_EPHEMERAL - | LIMIT_ANNOUNCES; + | LIMIT_ANNOUNCES + | ANALYZE_SPENDS; diff --git a/src/gen/mod.rs b/src/gen/mod.rs index f4775ef0c..c835afa63 100644 --- a/src/gen/mod.rs +++ b/src/gen/mod.rs @@ -4,6 +4,7 @@ pub mod conditions; pub mod flags; pub mod get_puzzle_and_solution; pub mod opcodes; +pub mod policy; pub mod run_block_generator; pub mod run_puzzle; pub mod sanitize_int; diff --git a/src/gen/policy.rs b/src/gen/policy.rs new file mode 100644 index 000000000..cbc51759e --- /dev/null +++ b/src/gen/policy.rs @@ -0,0 +1,11 @@ +use crate::gen::conditions::{Condition, Spend}; +use clvmr::allocator::Allocator; + +// These are customization points for the condition parsing and validation. The +// mempool wants to record additional information than plain consensus +// validation, so it hooks into these. +pub trait ConditionPolicy { + fn new_spend(&mut self, spend: &mut Spend); + fn condition(&mut self, spend: &mut Spend, c: &Condition); + fn post_spend(&mut self, a: &Allocator, spend: &mut Spend); +} diff --git a/src/gen/run_block_generator.rs b/src/gen/run_block_generator.rs index 057ef3d41..9b712a183 100644 --- a/src/gen/run_block_generator.rs +++ b/src/gen/run_block_generator.rs @@ -2,6 +2,7 @@ use crate::gen::conditions::{ parse_spends, process_single_spend, validate_conditions, ParseState, SpendBundleConditions, }; use crate::gen::flags::ALLOW_BACKREFS; +use crate::gen::policy::ConditionPolicy; use crate::gen::validation_error::{first, ErrorCode, ValidationErr}; use crate::generator_rom::{CLVM_DESERIALIZER, COST_PER_BYTE, GENERATOR_ROM}; use clvm_utils::tree_hash; @@ -35,12 +36,13 @@ fn subtract_cost(a: &Allocator, cost_left: &mut Cost, subtract: Cost) -> Result< // the only reason we need to pass in the allocator is because the returned // SpendBundleConditions contains NodePtr fields. If that's changed, we could // create the allocator inside this functions as well. -pub fn run_block_generator>( +pub fn run_block_generator, T: ConditionPolicy>( a: &mut Allocator, program: &[u8], block_refs: &[GenBuf], max_cost: u64, flags: u32, + policy: &mut T, ) -> Result { let mut cost_left = max_cost; let byte_cost = program.len() as u64 * COST_PER_BYTE; @@ -74,7 +76,7 @@ pub fn run_block_generator>( // we pass in what's left of max_cost here, to fail early in case the // cost of a condition brings us over the cost limit - let mut result = parse_spends(a, generator_output, cost_left, flags)?; + let mut result = parse_spends(a, generator_output, cost_left, flags, policy)?; result.cost += max_cost - cost_left; Ok(result) } @@ -108,12 +110,13 @@ pub fn extract_n( // you only pay cost for the generator, the puzzles and the conditions). // it also does not apply the stack depth or object allocation limits the same, // as each puzzle run in its own environment. -pub fn run_block_generator2>( +pub fn run_block_generator2, T: ConditionPolicy>( a: &mut Allocator, program: &[u8], block_refs: &[GenBuf], max_cost: u64, flags: u32, + policy: &mut T, ) -> Result { let byte_cost = program.len() as u64 * COST_PER_BYTE; @@ -178,6 +181,7 @@ pub fn run_block_generator2>( conditions, flags, &mut cost_left, + policy, )?; } if a.atom_len(all_spends) != 0 { diff --git a/src/gen/run_puzzle.rs b/src/gen/run_puzzle.rs index 702fdc637..b35907cc6 100644 --- a/src/gen/run_puzzle.rs +++ b/src/gen/run_puzzle.rs @@ -1,5 +1,6 @@ use crate::gen::conditions::{parse_conditions, ParseState, Spend, SpendBundleConditions}; use crate::gen::flags::ALLOW_BACKREFS; +use crate::gen::policy::ConditionPolicy; use crate::gen::validation_error::ValidationErr; use chia_protocol::bytes::Bytes32; use chia_protocol::coin::Coin; @@ -11,7 +12,8 @@ use clvmr::run_program::run_program; use clvmr::serde::{node_from_bytes, node_from_bytes_backrefs}; use std::sync::Arc; -pub fn run_puzzle( +#[allow(clippy::too_many_arguments)] +pub fn run_puzzle( a: &mut Allocator, puzzle: &[u8], solution: &[u8], @@ -19,6 +21,7 @@ pub fn run_puzzle( amount: u64, max_cost: u64, flags: u32, + policy: &mut T, ) -> Result { let deserialize = if (flags & ALLOW_BACKREFS) != 0 { node_from_bytes_backrefs @@ -48,13 +51,15 @@ pub fn run_puzzle( .into(), ); - let spend = Spend::new( + let mut spend = Spend::new( a.new_atom(parent_id)?, amount, a.new_atom(&puzzle_hash)?, coin_id, ); + policy.new_spend(&mut spend); + let mut cost_left = max_cost - clvm_cost; parse_conditions( @@ -65,6 +70,7 @@ pub fn run_puzzle( conditions, flags, &mut cost_left, + policy, )?; ret.cost = max_cost - cost_left; Ok(ret) diff --git a/src/gen/test_generators.rs b/src/gen/test_generators.rs index e55ca8878..bc6c42809 100644 --- a/src/gen/test_generators.rs +++ b/src/gen/test_generators.rs @@ -1,4 +1,4 @@ -use super::conditions::{NewCoin, Spend, SpendBundleConditions}; +use super::conditions::{MempoolPolicy, NewCoin, Spend, SpendBundleConditions}; use super::run_block_generator::{run_block_generator, run_block_generator2}; use crate::allocator::make_allocator; use crate::gen::flags::{ALLOW_BACKREFS, MEMPOOL_MODE}; @@ -210,30 +210,42 @@ fn run_generator(#[case] name: &str) { for (flags, expected) in zip(&[ALLOW_BACKREFS, ALLOW_BACKREFS | MEMPOOL_MODE], expected) { println!("flags: {:x}", flags); let mut a = make_allocator(*flags); - let (expected_cost, output) = - match run_block_generator(&mut a, &generator, &block_refs, 11000000000, *flags) { - Ok(conditions) => (conditions.cost, print_conditions(&a, &conditions)), - Err(code) => (0, format!("FAILED: {}\n", u32::from(code.1))), - }; - - let output_hard_fork = - match run_block_generator2(&mut a, &generator, &block_refs, 11000000000, *flags) { - Ok(mut conditions) => { - // in the hard fork, the cost of running the genrator + - // puzzles should never be higher than before the hard-fork - // but it's likely less. - assert!(conditions.cost <= expected_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 = expected_cost; - print_conditions(&a, &conditions) - } - Err(code) => { - format!("FAILED: {}\n", u32::from(code.1)) - } - }; + let (expected_cost, output) = match run_block_generator( + &mut a, + &generator, + &block_refs, + 11000000000, + *flags, + &mut MempoolPolicy::default(), + ) { + Ok(conditions) => (conditions.cost, print_conditions(&a, &conditions)), + Err(code) => (0, format!("FAILED: {}\n", u32::from(code.1))), + }; + + let output_hard_fork = match run_block_generator2( + &mut a, + &generator, + &block_refs, + 11000000000, + *flags, + &mut MempoolPolicy::default(), + ) { + Ok(mut conditions) => { + // in the hard fork, the cost of running the genrator + + // puzzles should never be higher than before the hard-fork + // but it's likely less. + assert!(conditions.cost <= expected_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 = expected_cost; + print_conditions(&a, &conditions) + } + Err(code) => { + format!("FAILED: {}\n", u32::from(code.1)) + } + }; if output != output_hard_fork { print_diff(&output, &output_hard_fork); diff --git a/wheel/src/api.rs b/wheel/src/api.rs index 009fe3021..859d58f2f 100644 --- a/wheel/src/api.rs +++ b/wheel/src/api.rs @@ -4,10 +4,11 @@ use crate::run_generator::{ PySpendBundleConditions, }; use chia::allocator::make_allocator; +use chia::gen::conditions::MempoolPolicy; use chia::gen::flags::{ - AGG_SIG_ARGS, ALLOW_BACKREFS, COND_ARGS_NIL, ENABLE_SOFTFORK_CONDITION, LIMIT_ANNOUNCES, - LIMIT_OBJECTS, MEMPOOL_MODE, NO_RELATIVE_CONDITIONS_ON_EPHEMERAL, NO_UNKNOWN_CONDS, - STRICT_ARGS_COUNT, + AGG_SIG_ARGS, ALLOW_BACKREFS, ANALYZE_SPENDS, COND_ARGS_NIL, ENABLE_SOFTFORK_CONDITION, + LIMIT_ANNOUNCES, LIMIT_OBJECTS, MEMPOOL_MODE, NO_RELATIVE_CONDITIONS_ON_EPHEMERAL, + NO_UNKNOWN_CONDS, STRICT_ARGS_COUNT, }; use chia::gen::run_puzzle::run_puzzle as native_run_puzzle; use chia::gen::solution_generator::solution_generator as native_solution_generator; @@ -160,7 +161,16 @@ fn run_puzzle( flags: u32, ) -> PyResult { let mut a = make_allocator(LIMIT_HEAP); - let conds = native_run_puzzle(&mut a, puzzle, solution, parent_id, amount, max_cost, flags)?; + let conds = native_run_puzzle( + &mut a, + puzzle, + solution, + parent_id, + amount, + max_cost, + flags, + &mut MempoolPolicy::default(), + )?; Ok(convert_spend_bundle_conds(&a, conds)) } @@ -341,6 +351,7 @@ pub fn chia_rs(py: Python, m: &PyModule) -> PyResult<()> { "ELIGIBLE_FOR_DEDUP", chia::gen::conditions::ELIGIBLE_FOR_DEDUP, )?; + m.add("ELIGIBLE_FOR_FF", chia::gen::conditions::ELIGIBLE_FOR_FF)?; m.add_class::()?; // clvm functions @@ -358,6 +369,7 @@ pub fn chia_rs(py: Python, m: &PyModule) -> PyResult<()> { m.add("MEMPOOL_MODE", MEMPOOL_MODE)?; m.add("LIMIT_OBJECTS", LIMIT_OBJECTS)?; m.add("ALLOW_BACKREFS", ALLOW_BACKREFS)?; + m.add("ANALYZE_SPENDS", ANALYZE_SPENDS)?; // Chia classes m.add_class::()?; diff --git a/wheel/src/run_generator.rs b/wheel/src/run_generator.rs index bf0571b19..240279125 100644 --- a/wheel/src/run_generator.rs +++ b/wheel/src/run_generator.rs @@ -1,5 +1,6 @@ use chia::allocator::make_allocator; -use chia::gen::conditions::{Spend, SpendBundleConditions}; +use chia::gen::conditions::{MempoolPolicy, NonePolicy, Spend, SpendBundleConditions}; +use chia::gen::flags::ANALYZE_SPENDS; use chia::gen::run_block_generator::run_block_generator as native_run_block_generator; use chia::gen::run_block_generator::run_block_generator2 as native_run_block_generator2; use chia::gen::validation_error::ValidationErr; @@ -166,7 +167,27 @@ pub fn run_block_generator( let program = unsafe { std::slice::from_raw_parts(program.buf_ptr() as *const u8, program.len_bytes()) }; - match native_run_block_generator(&mut allocator, program, &refs, max_cost, flags) { + let r = if (flags & ANALYZE_SPENDS) == 0 { + native_run_block_generator( + &mut allocator, + program, + &refs, + max_cost, + flags, + &mut NonePolicy::default(), + ) + } else { + native_run_block_generator( + &mut allocator, + program, + &refs, + max_cost, + flags, + &mut MempoolPolicy::default(), + ) + }; + + match r { Ok(spend_bundle_conds) => { // everything was successful Ok(( @@ -209,7 +230,27 @@ pub fn run_block_generator2( let program = unsafe { std::slice::from_raw_parts(program.buf_ptr() as *const u8, program.len_bytes()) }; - match native_run_block_generator2(&mut allocator, program, &refs, max_cost, flags) { + let r = if (flags & ANALYZE_SPENDS) == 0 { + native_run_block_generator2( + &mut allocator, + program, + &refs, + max_cost, + flags, + &mut NonePolicy::default(), + ) + } else { + native_run_block_generator2( + &mut allocator, + program, + &refs, + max_cost, + flags, + &mut MempoolPolicy::default(), + ) + }; + + match r { Ok(spend_bundle_conds) => { // everything was successful Ok(( From 736055152006d996bfd9033755cfbbbc8f309f67 Mon Sep 17 00:00:00 2001 From: arvidn Date: Wed, 25 Oct 2023 20:37:02 +0200 Subject: [PATCH 2/2] rename Policy -> Visitor. construct one per spend --- chia-tools/src/bin/analyze-chain.rs | 5 +- chia-tools/src/bin/test-block-generators.rs | 20 ++--- fuzz/fuzz_targets/fast-forward.rs | 8 +- fuzz/fuzz_targets/parse-conditions.rs | 8 +- fuzz/fuzz_targets/parse-spends.rs | 10 +-- fuzz/fuzz_targets/process-spend.rs | 5 +- fuzz/fuzz_targets/run-generator.rs | 15 +--- fuzz/fuzz_targets/run-puzzle.rs | 5 +- src/fast_forward.rs | 8 +- src/gen/conditions.rs | 55 +++++++----- src/gen/mod.rs | 2 +- src/gen/run_block_generator.rs | 13 ++- src/gen/run_puzzle.rs | 10 +-- src/gen/{policy.rs => spend_visitor.rs} | 4 +- src/gen/test_generators.rs | 15 ++-- wheel/src/api.rs | 13 +-- wheel/src/run_generator.rs | 98 ++++++++------------- 17 files changed, 121 insertions(+), 173 deletions(-) rename src/gen/{policy.rs => spend_visitor.rs} (84%) diff --git a/chia-tools/src/bin/analyze-chain.rs b/chia-tools/src/bin/analyze-chain.rs index 8e6a82778..a924f0069 100644 --- a/chia-tools/src/bin/analyze-chain.rs +++ b/chia-tools/src/bin/analyze-chain.rs @@ -7,7 +7,7 @@ use std::time::SystemTime; use sqlite::State; -use chia::gen::conditions::{parse_spends, MempoolPolicy}; +use chia::gen::conditions::{parse_spends, MempoolVisitor}; use chia::gen::flags::MEMPOOL_MODE; use chia::gen::validation_error::ValidationErr; use chia::generator_rom::{COST_PER_BYTE, GENERATOR_ROM}; @@ -183,12 +183,11 @@ fn main() { let start_conditions = SystemTime::now(); // we pass in what's left of max_cost here, to fail early in case the // cost of a condition brings us over the cost limit - let conds = match parse_spends( + let conds = match parse_spends::( &a, generator_output, ti.cost - clvm_cost, MEMPOOL_MODE, - &mut MempoolPolicy::default(), ) { Err(ValidationErr(_, _)) => { panic!("failed to parse conditions in block {height}"); diff --git a/chia-tools/src/bin/test-block-generators.rs b/chia-tools/src/bin/test-block-generators.rs index 225594602..ff059e3be 100644 --- a/chia-tools/src/bin/test-block-generators.rs +++ b/chia-tools/src/bin/test-block-generators.rs @@ -5,7 +5,7 @@ use chia_traits::Streamable; use sqlite::State; -use chia::gen::conditions::{NewCoin, NonePolicy, Spend, SpendBundleConditions}; +use chia::gen::conditions::{EmptyVisitor, NewCoin, Spend, SpendBundleConditions}; use chia::gen::flags::{ALLOW_BACKREFS, MEMPOOL_MODE}; use chia::gen::run_block_generator::{run_block_generator, run_block_generator2}; use clvmr::allocator::NodePtr; @@ -157,9 +157,9 @@ fn main() { }; let block_runner = if args.rust_generator { - run_block_generator2::<_, NonePolicy> + run_block_generator2::<_, EmptyVisitor> } else { - run_block_generator::<_, NonePolicy> + run_block_generator::<_, EmptyVisitor> }; while let Ok(State::Row) = statement.next() { @@ -239,15 +239,8 @@ fn main() { prg.as_ref() }; - let mut conditions = block_runner( - &mut a, - generator, - &block_refs, - ti.cost, - flags, - &mut NonePolicy::default(), - ) - .expect("failed to run block generator"); + let mut conditions = block_runner(&mut a, generator, &block_refs, ti.cost, flags) + .expect("failed to run block generator"); if args.rust_generator || args.test_backrefs { assert!(conditions.cost <= ti.cost); @@ -262,13 +255,12 @@ fn main() { } if args.validate { - let mut baseline = run_block_generator( + let mut baseline = run_block_generator::<_, EmptyVisitor>( &mut a, prg.as_ref(), &block_refs, ti.cost, 0, - &mut NonePolicy::default(), ) .expect("failed to run block generator"); assert_eq!(baseline.cost, ti.cost); diff --git a/fuzz/fuzz_targets/fast-forward.rs b/fuzz/fuzz_targets/fast-forward.rs index de8360385..47efac326 100644 --- a/fuzz/fuzz_targets/fast-forward.rs +++ b/fuzz/fuzz_targets/fast-forward.rs @@ -1,6 +1,6 @@ #![no_main] use chia::fast_forward::fast_forward_singleton; -use chia::gen::conditions::MempoolPolicy; +use chia::gen::conditions::MempoolVisitor; use chia::gen::run_puzzle::run_puzzle; use chia::gen::validation_error::ValidationErr; use chia_protocol::Bytes32; @@ -57,7 +57,7 @@ fuzz_target!(|data: &[u8]| { let new_solution = node_to_bytes(&a, new_solution).expect("serialize new solution"); // run original spend - let conditions1 = run_puzzle( + let conditions1 = run_puzzle::( &mut a, spend.puzzle_reveal.as_slice(), spend.solution.as_slice(), @@ -65,11 +65,10 @@ fuzz_target!(|data: &[u8]| { spend.coin.amount, 11000000000, 0, - &mut MempoolPolicy::default(), ); // run new spend - let conditions2 = run_puzzle( + let conditions2 = run_puzzle::( &mut a, spend.puzzle_reveal.as_slice(), new_solution.as_slice(), @@ -77,7 +76,6 @@ fuzz_target!(|data: &[u8]| { new_coin.amount, 11000000000, 0, - &mut MempoolPolicy::default(), ); match (conditions1, conditions2) { diff --git a/fuzz/fuzz_targets/parse-conditions.rs b/fuzz/fuzz_targets/parse-conditions.rs index e8c1875da..b3cc662cd 100644 --- a/fuzz/fuzz_targets/parse-conditions.rs +++ b/fuzz/fuzz_targets/parse-conditions.rs @@ -2,8 +2,9 @@ use libfuzzer_sys::fuzz_target; use chia::gen::conditions::{ - parse_conditions, MempoolPolicy, ParseState, Spend, SpendBundleConditions, + parse_conditions, MempoolVisitor, ParseState, Spend, SpendBundleConditions, }; +use chia::gen::spend_visitor::SpendVisitor; use chia_protocol::Bytes32; use chia_protocol::Coin; use clvm_utils::tree_hash; @@ -44,7 +45,7 @@ fuzz_target!(|data: &[u8]| { NO_UNKNOWN_CONDS, ENABLE_SOFTFORK_CONDITION, ] { - let coin_spend = Spend { + let mut coin_spend = Spend { parent_id: a.new_atom(&parent_id).expect("atom failed"), coin_amount: amount, puzzle_hash: a.new_atom(&puzzle_hash).expect("atom failed"), @@ -65,6 +66,7 @@ fuzz_target!(|data: &[u8]| { agg_sig_parent_puzzle: Vec::new(), flags: 0_u32, }; + let mut visitor = MempoolVisitor::new_spend(&mut coin_spend); let mut max_cost: u64 = 3300000000; let _ret = parse_conditions( &a, @@ -74,7 +76,7 @@ fuzz_target!(|data: &[u8]| { input, *flags, &mut max_cost, - &mut MempoolPolicy::default(), + &mut visitor, ); } }); diff --git a/fuzz/fuzz_targets/parse-spends.rs b/fuzz/fuzz_targets/parse-spends.rs index c0b1378d6..4b0e94951 100644 --- a/fuzz/fuzz_targets/parse-spends.rs +++ b/fuzz/fuzz_targets/parse-spends.rs @@ -1,7 +1,7 @@ #![no_main] use libfuzzer_sys::fuzz_target; -use chia::gen::conditions::{parse_spends, MempoolPolicy}; +use chia::gen::conditions::{parse_spends, MempoolVisitor}; use clvmr::allocator::Allocator; use fuzzing_utils::{make_tree, BitCursor}; @@ -11,12 +11,6 @@ fuzz_target!(|data: &[u8]| { let mut a = Allocator::new(); let input = make_tree(&mut a, &mut BitCursor::new(data), false); for flags in &[0, COND_ARGS_NIL, STRICT_ARGS_COUNT, NO_UNKNOWN_CONDS] { - let _ret = parse_spends( - &a, - input, - 33000000000, - *flags, - &mut MempoolPolicy::default(), - ); + let _ret = parse_spends::(&a, input, 33000000000, *flags); } }); diff --git a/fuzz/fuzz_targets/process-spend.rs b/fuzz/fuzz_targets/process-spend.rs index 1e43222d3..bd57b7a26 100644 --- a/fuzz/fuzz_targets/process-spend.rs +++ b/fuzz/fuzz_targets/process-spend.rs @@ -1,6 +1,6 @@ #![no_main] use chia::gen::conditions::{ - process_single_spend, MempoolPolicy, ParseState, SpendBundleConditions, + process_single_spend, MempoolVisitor, ParseState, SpendBundleConditions, }; use chia::gen::flags::{COND_ARGS_NIL, NO_UNKNOWN_CONDS, STRICT_ARGS_COUNT}; use clvmr::allocator::Allocator; @@ -20,7 +20,7 @@ fuzz_target!(|data: &[u8]| { for flags in &[0, COND_ARGS_NIL, STRICT_ARGS_COUNT, NO_UNKNOWN_CONDS] { let mut cost_left = 11000000; - let _ = process_single_spend( + let _ = process_single_spend::( &a, &mut ret, &mut state, @@ -30,7 +30,6 @@ fuzz_target!(|data: &[u8]| { conds, *flags, &mut cost_left, - &mut MempoolPolicy::default(), ); } }); diff --git a/fuzz/fuzz_targets/run-generator.rs b/fuzz/fuzz_targets/run-generator.rs index f23b3ade8..dc740e21d 100644 --- a/fuzz/fuzz_targets/run-generator.rs +++ b/fuzz/fuzz_targets/run-generator.rs @@ -1,6 +1,6 @@ #![no_main] use chia::allocator::make_allocator; -use chia::gen::conditions::MempoolPolicy; +use chia::gen::conditions::MempoolVisitor; use chia::gen::flags::{ALLOW_BACKREFS, LIMIT_OBJECTS}; use chia::gen::run_block_generator::{run_block_generator, run_block_generator2}; use chia::gen::validation_error::{ErrorCode, ValidationErr}; @@ -9,24 +9,17 @@ use libfuzzer_sys::fuzz_target; fuzz_target!(|data: &[u8]| { let mut a1 = make_allocator(LIMIT_HEAP | LIMIT_OBJECTS); - let r1 = run_block_generator::<&[u8], MempoolPolicy>( - &mut a1, - data, - &[], - 110000000, - ALLOW_BACKREFS, - &mut MempoolPolicy::default(), - ); + let r1 = + run_block_generator::<&[u8], MempoolVisitor>(&mut a1, data, &[], 110000000, ALLOW_BACKREFS); drop(a1); let mut a2 = make_allocator(LIMIT_HEAP | LIMIT_OBJECTS); - let r2 = run_block_generator2::<&[u8], MempoolPolicy>( + let r2 = run_block_generator2::<&[u8], MempoolVisitor>( &mut a2, data, &[], 110000000, ALLOW_BACKREFS, - &mut MempoolPolicy::default(), ); drop(a2); diff --git a/fuzz/fuzz_targets/run-puzzle.rs b/fuzz/fuzz_targets/run-puzzle.rs index 75b3ee67c..d9a0c7513 100644 --- a/fuzz/fuzz_targets/run-puzzle.rs +++ b/fuzz/fuzz_targets/run-puzzle.rs @@ -1,5 +1,5 @@ #![no_main] -use chia::gen::conditions::MempoolPolicy; +use chia::gen::conditions::MempoolVisitor; use chia::gen::flags::ALLOW_BACKREFS; use chia::gen::run_puzzle::run_puzzle; use chia_protocol::CoinSpend; @@ -14,7 +14,7 @@ fuzz_target!(|data: &[u8]| { let Ok(spend) = CoinSpend::parse(&mut Cursor::new(data)) else { return; }; - let _ = run_puzzle( + let _ = run_puzzle::( &mut a, spend.puzzle_reveal.as_slice(), spend.solution.as_slice(), @@ -22,6 +22,5 @@ fuzz_target!(|data: &[u8]| { spend.coin.amount, 11000000000, ALLOW_BACKREFS, - &mut MempoolPolicy::default(), ); }); diff --git a/src/fast_forward.rs b/src/fast_forward.rs index 3b22268cf..fa5a619a2 100644 --- a/src/fast_forward.rs +++ b/src/fast_forward.rs @@ -182,7 +182,7 @@ pub fn fast_forward_singleton( #[cfg(test)] mod tests { use super::*; - use crate::gen::conditions::MempoolPolicy; + use crate::gen::conditions::MempoolVisitor; use crate::gen::run_puzzle::run_puzzle; use chia_protocol::CoinSpend; use chia_traits::streamable::Streamable; @@ -242,7 +242,7 @@ mod tests { let new_solution = node_to_bytes(&a, new_solution).expect("serialize new solution"); // run original spend - let conditions1 = run_puzzle( + let conditions1 = run_puzzle::( &mut a, spend.puzzle_reveal.as_slice(), spend.solution.as_slice(), @@ -250,12 +250,11 @@ mod tests { spend.coin.amount, 11000000000, 0, - &mut MempoolPolicy::default(), ) .expect("run_puzzle"); // run new spend - let conditions2 = run_puzzle( + let conditions2 = run_puzzle::( &mut a, spend.puzzle_reveal.as_slice(), new_solution.as_slice(), @@ -263,7 +262,6 @@ mod tests { new_coin.amount, 11000000000, 0, - &mut MempoolPolicy::default(), ) .expect("run_puzzle"); diff --git a/src/gen/conditions.rs b/src/gen/conditions.rs index 14c39bb6f..80c0d0a19 100644 --- a/src/gen/conditions.rs +++ b/src/gen/conditions.rs @@ -18,7 +18,7 @@ use crate::gen::flags::{ AGG_SIG_ARGS, COND_ARGS_NIL, LIMIT_ANNOUNCES, NO_RELATIVE_CONDITIONS_ON_EPHEMERAL, NO_UNKNOWN_CONDS, STRICT_ARGS_COUNT, }; -use crate::gen::policy::ConditionPolicy; +use crate::gen::spend_visitor::SpendVisitor; use crate::gen::validation_error::check_nil; use chia_protocol::bytes::Bytes32; use clvmr::allocator::{Allocator, NodePtr, SExp}; @@ -47,22 +47,22 @@ pub const HAS_RELATIVE_CONDITION: u32 = 2; // 4. it has an output coin with the same puzzle hash and amount as the spend itself pub const ELIGIBLE_FOR_FF: u32 = 4; -#[derive(Default)] -pub struct NonePolicy {} +pub struct EmptyVisitor {} -impl ConditionPolicy for NonePolicy { - fn new_spend(&mut self, _spend: &mut Spend) {} +impl SpendVisitor for EmptyVisitor { + fn new_spend(_spend: &mut Spend) -> Self { + Self {} + } fn condition(&mut self, _spend: &mut Spend, _c: &Condition) {} fn post_spend(&mut self, _a: &Allocator, _spend: &mut Spend) {} } -#[derive(Default)] -pub struct MempoolPolicy { +pub struct MempoolVisitor { condition_counter: i32, } -impl ConditionPolicy for MempoolPolicy { - fn new_spend(&mut self, spend: &mut Spend) { +impl SpendVisitor for MempoolVisitor { + fn new_spend(spend: &mut Spend) -> Self { // assume it's eligibe. We'll clear this flag if it isn't let mut spend_flags = ELIGIBLE_FOR_DEDUP; @@ -71,7 +71,10 @@ impl ConditionPolicy for MempoolPolicy { spend_flags |= ELIGIBLE_FOR_FF; } spend.flags |= spend_flags; - self.condition_counter = 0; + + Self { + condition_counter: 0, + } } fn condition(&mut self, spend: &mut Spend, c: &Condition) { @@ -754,7 +757,7 @@ pub(crate) fn parse_single_spend( } #[allow(clippy::too_many_arguments)] -pub fn process_single_spend( +pub fn process_single_spend( a: &Allocator, ret: &mut SpendBundleConditions, state: &mut ParseState, @@ -764,7 +767,6 @@ pub fn process_single_spend( conditions: NodePtr, flags: u32, max_cost: &mut Cost, - policy: &mut T, ) -> Result<(), ValidationErr> { let parent_id = sanitize_hash(a, parent_id, 32, ErrorCode::InvalidParentId)?; let puzzle_hash = sanitize_hash(a, puzzle_hash, 32, ErrorCode::InvalidPuzzleHash)?; @@ -789,9 +791,18 @@ pub fn process_single_spend( let mut spend = Spend::new(parent_id, my_amount, puzzle_hash, coin_id); - policy.new_spend(&mut spend); + let mut visitor = V::new_spend(&mut spend); - parse_conditions(a, ret, state, spend, conditions, flags, max_cost, policy) + parse_conditions( + a, + ret, + state, + spend, + conditions, + flags, + max_cost, + &mut visitor, + ) } fn assert_not_ephemeral(spend_flags: &mut u32, state: &mut ParseState, idx: usize) { @@ -813,7 +824,7 @@ fn decrement(cnt: &mut u32, n: NodePtr) -> Result<(), ValidationErr> { } #[allow(clippy::too_many_arguments)] -pub fn parse_conditions( +pub fn parse_conditions( a: &Allocator, ret: &mut SpendBundleConditions, state: &mut ParseState, @@ -821,7 +832,7 @@ pub fn parse_conditions( mut iter: NodePtr, flags: u32, max_cost: &mut Cost, - policy: &mut T, + visitor: &mut V, ) -> Result<(), ValidationErr> { let mut announce_countdown: u32 = if (flags & LIMIT_ANNOUNCES) != 0 { 1024 @@ -869,7 +880,7 @@ pub fn parse_conditions( } c = rest(a, c)?; let cva = parse_args(a, c, op, flags)?; - policy.condition(&mut spend, &cva); + visitor.condition(&mut spend, &cva); match cva { Condition::ReserveFee(limit) => { // reserve fees are accumulated @@ -1097,7 +1108,7 @@ pub fn parse_conditions( } } - policy.post_spend(a, &mut spend); + visitor.post_spend(a, &mut spend); ret.spends.push(spend); Ok(()) @@ -1130,12 +1141,11 @@ fn is_ephemeral( // This function parses, and validates aspects of, the above structure and // returns a list of all spends, along with all conditions, organized by // condition op-code -pub fn parse_spends( +pub fn parse_spends( a: &Allocator, spends: NodePtr, max_cost: Cost, flags: u32, - policy: &mut T, ) -> Result { let mut ret = SpendBundleConditions::default(); let mut state = ParseState::default(); @@ -1152,7 +1162,7 @@ pub fn parse_spends( // as well as updates it with any conditions let (parent_id, puzzle_hash, amount, conds) = parse_single_spend(a, spend)?; - process_single_spend( + process_single_spend::( a, &mut ret, &mut state, @@ -1162,7 +1172,6 @@ pub fn parse_spends( conds, flags, &mut cost_left, - policy, )?; } @@ -1567,7 +1576,7 @@ fn cond_test_cb( print!("{:02x}", c); } println!(); - match parse_spends(&a, n, 11000000000, flags, &mut MempoolPolicy::default()) { + match parse_spends::(&a, n, 11000000000, flags) { Ok(list) => { for n in &list.spends { println!("{:?}", n); diff --git a/src/gen/mod.rs b/src/gen/mod.rs index c835afa63..033ebd148 100644 --- a/src/gen/mod.rs +++ b/src/gen/mod.rs @@ -4,11 +4,11 @@ pub mod conditions; pub mod flags; pub mod get_puzzle_and_solution; pub mod opcodes; -pub mod policy; pub mod run_block_generator; pub mod run_puzzle; pub mod sanitize_int; pub mod solution_generator; +pub mod spend_visitor; pub mod validation_error; // these tests are large and expensive. They take a long time to run in diff --git a/src/gen/run_block_generator.rs b/src/gen/run_block_generator.rs index 9b712a183..cc4bcd272 100644 --- a/src/gen/run_block_generator.rs +++ b/src/gen/run_block_generator.rs @@ -2,7 +2,7 @@ use crate::gen::conditions::{ parse_spends, process_single_spend, validate_conditions, ParseState, SpendBundleConditions, }; use crate::gen::flags::ALLOW_BACKREFS; -use crate::gen::policy::ConditionPolicy; +use crate::gen::spend_visitor::SpendVisitor; use crate::gen::validation_error::{first, ErrorCode, ValidationErr}; use crate::generator_rom::{CLVM_DESERIALIZER, COST_PER_BYTE, GENERATOR_ROM}; use clvm_utils::tree_hash; @@ -36,13 +36,12 @@ fn subtract_cost(a: &Allocator, cost_left: &mut Cost, subtract: Cost) -> Result< // the only reason we need to pass in the allocator is because the returned // SpendBundleConditions contains NodePtr fields. If that's changed, we could // create the allocator inside this functions as well. -pub fn run_block_generator, T: ConditionPolicy>( +pub fn run_block_generator, V: SpendVisitor>( a: &mut Allocator, program: &[u8], block_refs: &[GenBuf], max_cost: u64, flags: u32, - policy: &mut T, ) -> Result { let mut cost_left = max_cost; let byte_cost = program.len() as u64 * COST_PER_BYTE; @@ -76,7 +75,7 @@ pub fn run_block_generator, T: ConditionPolicy>( // we pass in what's left of max_cost here, to fail early in case the // cost of a condition brings us over the cost limit - let mut result = parse_spends(a, generator_output, cost_left, flags, policy)?; + let mut result = parse_spends::(a, generator_output, cost_left, flags)?; result.cost += max_cost - cost_left; Ok(result) } @@ -110,13 +109,12 @@ pub fn extract_n( // you only pay cost for the generator, the puzzles and the conditions). // it also does not apply the stack depth or object allocation limits the same, // as each puzzle run in its own environment. -pub fn run_block_generator2, T: ConditionPolicy>( +pub fn run_block_generator2, V: SpendVisitor>( a: &mut Allocator, program: &[u8], block_refs: &[GenBuf], max_cost: u64, flags: u32, - policy: &mut T, ) -> Result { let byte_cost = program.len() as u64 * COST_PER_BYTE; @@ -171,7 +169,7 @@ pub fn run_block_generator2, T: ConditionPolicy>( let buf = tree_hash(a, puzzle); let puzzle_hash = a.new_atom(&buf)?; - process_single_spend( + process_single_spend::( a, &mut ret, &mut state, @@ -181,7 +179,6 @@ pub fn run_block_generator2, T: ConditionPolicy>( conditions, flags, &mut cost_left, - policy, )?; } if a.atom_len(all_spends) != 0 { diff --git a/src/gen/run_puzzle.rs b/src/gen/run_puzzle.rs index b35907cc6..4ee5a79d6 100644 --- a/src/gen/run_puzzle.rs +++ b/src/gen/run_puzzle.rs @@ -1,6 +1,6 @@ use crate::gen::conditions::{parse_conditions, ParseState, Spend, SpendBundleConditions}; use crate::gen::flags::ALLOW_BACKREFS; -use crate::gen::policy::ConditionPolicy; +use crate::gen::spend_visitor::SpendVisitor; use crate::gen::validation_error::ValidationErr; use chia_protocol::bytes::Bytes32; use chia_protocol::coin::Coin; @@ -12,8 +12,7 @@ use clvmr::run_program::run_program; use clvmr::serde::{node_from_bytes, node_from_bytes_backrefs}; use std::sync::Arc; -#[allow(clippy::too_many_arguments)] -pub fn run_puzzle( +pub fn run_puzzle( a: &mut Allocator, puzzle: &[u8], solution: &[u8], @@ -21,7 +20,6 @@ pub fn run_puzzle( amount: u64, max_cost: u64, flags: u32, - policy: &mut T, ) -> Result { let deserialize = if (flags & ALLOW_BACKREFS) != 0 { node_from_bytes_backrefs @@ -58,7 +56,7 @@ pub fn run_puzzle( coin_id, ); - policy.new_spend(&mut spend); + let mut visitor = V::new_spend(&mut spend); let mut cost_left = max_cost - clvm_cost; @@ -70,7 +68,7 @@ pub fn run_puzzle( conditions, flags, &mut cost_left, - policy, + &mut visitor, )?; ret.cost = max_cost - cost_left; Ok(ret) diff --git a/src/gen/policy.rs b/src/gen/spend_visitor.rs similarity index 84% rename from src/gen/policy.rs rename to src/gen/spend_visitor.rs index cbc51759e..2e8299218 100644 --- a/src/gen/policy.rs +++ b/src/gen/spend_visitor.rs @@ -4,8 +4,8 @@ use clvmr::allocator::Allocator; // These are customization points for the condition parsing and validation. The // mempool wants to record additional information than plain consensus // validation, so it hooks into these. -pub trait ConditionPolicy { - fn new_spend(&mut self, spend: &mut Spend); +pub trait SpendVisitor { + fn new_spend(spend: &mut Spend) -> Self; fn condition(&mut self, spend: &mut Spend, c: &Condition); fn post_spend(&mut self, a: &Allocator, spend: &mut Spend); } diff --git a/src/gen/test_generators.rs b/src/gen/test_generators.rs index bc6c42809..500afa153 100644 --- a/src/gen/test_generators.rs +++ b/src/gen/test_generators.rs @@ -1,4 +1,4 @@ -use super::conditions::{MempoolPolicy, NewCoin, Spend, SpendBundleConditions}; +use super::conditions::{MempoolVisitor, NewCoin, Spend, SpendBundleConditions}; use super::run_block_generator::{run_block_generator, run_block_generator2}; use crate::allocator::make_allocator; use crate::gen::flags::{ALLOW_BACKREFS, MEMPOOL_MODE}; @@ -210,26 +210,27 @@ fn run_generator(#[case] name: &str) { for (flags, expected) in zip(&[ALLOW_BACKREFS, ALLOW_BACKREFS | MEMPOOL_MODE], expected) { println!("flags: {:x}", flags); let mut a = make_allocator(*flags); - let (expected_cost, output) = match run_block_generator( + let conds = run_block_generator::<_, MempoolVisitor>( &mut a, &generator, &block_refs, 11000000000, *flags, - &mut MempoolPolicy::default(), - ) { + ); + + let (expected_cost, output) = match conds { Ok(conditions) => (conditions.cost, print_conditions(&a, &conditions)), Err(code) => (0, format!("FAILED: {}\n", u32::from(code.1))), }; - let output_hard_fork = match run_block_generator2( + let conds = run_block_generator2::<_, MempoolVisitor>( &mut a, &generator, &block_refs, 11000000000, *flags, - &mut MempoolPolicy::default(), - ) { + ); + let output_hard_fork = match conds { Ok(mut conditions) => { // in the hard fork, the cost of running the genrator + // puzzles should never be higher than before the hard-fork diff --git a/wheel/src/api.rs b/wheel/src/api.rs index 859d58f2f..4afcbb558 100644 --- a/wheel/src/api.rs +++ b/wheel/src/api.rs @@ -4,7 +4,7 @@ use crate::run_generator::{ PySpendBundleConditions, }; use chia::allocator::make_allocator; -use chia::gen::conditions::MempoolPolicy; +use chia::gen::conditions::MempoolVisitor; use chia::gen::flags::{ AGG_SIG_ARGS, ALLOW_BACKREFS, ANALYZE_SPENDS, COND_ARGS_NIL, ENABLE_SOFTFORK_CONDITION, LIMIT_ANNOUNCES, LIMIT_OBJECTS, MEMPOOL_MODE, NO_RELATIVE_CONDITIONS_ON_EPHEMERAL, @@ -161,15 +161,8 @@ fn run_puzzle( flags: u32, ) -> PyResult { let mut a = make_allocator(LIMIT_HEAP); - let conds = native_run_puzzle( - &mut a, - puzzle, - solution, - parent_id, - amount, - max_cost, - flags, - &mut MempoolPolicy::default(), + let conds = native_run_puzzle::( + &mut a, puzzle, solution, parent_id, amount, max_cost, flags, )?; Ok(convert_spend_bundle_conds(&a, conds)) } diff --git a/wheel/src/run_generator.rs b/wheel/src/run_generator.rs index 240279125..57e93afd9 100644 --- a/wheel/src/run_generator.rs +++ b/wheel/src/run_generator.rs @@ -1,5 +1,5 @@ use chia::allocator::make_allocator; -use chia::gen::conditions::{MempoolPolicy, NonePolicy, Spend, SpendBundleConditions}; +use chia::gen::conditions::{EmptyVisitor, MempoolVisitor, Spend, SpendBundleConditions}; use chia::gen::flags::ANALYZE_SPENDS; use chia::gen::run_block_generator::run_block_generator as native_run_block_generator; use chia::gen::run_block_generator::run_block_generator2 as native_run_block_generator2; @@ -167,39 +167,27 @@ pub fn run_block_generator( let program = unsafe { std::slice::from_raw_parts(program.buf_ptr() as *const u8, program.len_bytes()) }; - let r = if (flags & ANALYZE_SPENDS) == 0 { - native_run_block_generator( - &mut allocator, - program, - &refs, - max_cost, - flags, - &mut NonePolicy::default(), - ) + let run_block = if (flags & ANALYZE_SPENDS) == 0 { + native_run_block_generator::<_, EmptyVisitor> } else { - native_run_block_generator( - &mut allocator, - program, - &refs, - max_cost, - flags, - &mut MempoolPolicy::default(), - ) + native_run_block_generator::<_, MempoolVisitor> }; - match r { - Ok(spend_bundle_conds) => { - // everything was successful - Ok(( - None, - Some(convert_spend_bundle_conds(&allocator, spend_bundle_conds)), - )) - } - Err(ValidationErr(_, error_code)) => { - // a validation error occurred - Ok((Some(error_code.into()), None)) - } - } + Ok( + match run_block(&mut allocator, program, &refs, max_cost, flags) { + Ok(spend_bundle_conds) => { + // everything was successful + ( + None, + Some(convert_spend_bundle_conds(&allocator, spend_bundle_conds)), + ) + } + Err(ValidationErr(_, error_code)) => { + // a validation error occurred + (Some(error_code.into()), None) + } + }, + ) } #[pyfunction] @@ -230,37 +218,25 @@ pub fn run_block_generator2( let program = unsafe { std::slice::from_raw_parts(program.buf_ptr() as *const u8, program.len_bytes()) }; - let r = if (flags & ANALYZE_SPENDS) == 0 { - native_run_block_generator2( - &mut allocator, - program, - &refs, - max_cost, - flags, - &mut NonePolicy::default(), - ) + let run_block = if (flags & ANALYZE_SPENDS) == 0 { + native_run_block_generator2::<_, EmptyVisitor> } else { - native_run_block_generator2( - &mut allocator, - program, - &refs, - max_cost, - flags, - &mut MempoolPolicy::default(), - ) + native_run_block_generator2::<_, MempoolVisitor> }; - match r { - Ok(spend_bundle_conds) => { - // everything was successful - Ok(( - None, - Some(convert_spend_bundle_conds(&allocator, spend_bundle_conds)), - )) - } - Err(ValidationErr(_, error_code)) => { - // a validation error occurred - Ok((Some(error_code.into()), None)) - } - } + Ok( + match run_block(&mut allocator, program, &refs, max_cost, flags) { + Ok(spend_bundle_conds) => { + // everything was successful + ( + None, + Some(convert_spend_bundle_conds(&allocator, spend_bundle_conds)), + ) + } + Err(ValidationErr(_, error_code)) => { + // a validation error occurred + (Some(error_code.into()), None) + } + }, + ) }