Skip to content

Commit

Permalink
Merge pull request #138 from Chia-Network/run-puzzle
Browse files Browse the repository at this point in the history
add new function run_puzzle()
  • Loading branch information
arvidn authored Feb 9, 2023
2 parents fc1883f + a5eef6a commit f3a4b0d
Show file tree
Hide file tree
Showing 12 changed files with 322 additions and 15 deletions.
7 changes: 7 additions & 0 deletions fuzz/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ cargo-fuzz = true
libfuzzer-sys = "0.4"
clvmr = "=0.2.3"
clvm-utils = { version = "=0.2.1", path = "../clvm-utils" }
chia-protocol = { version = "=0.2.1", path = "../chia-protocol" }

[dependencies.chia]
path = ".."
Expand Down Expand Up @@ -49,3 +50,9 @@ name = "self-extractor"
path = "fuzz_targets/self-extractor.rs"
test = false
doc = false

[[bin]]
name = "parse-spends"
path = "fuzz_targets/parse-spends.rs"
test = false
doc = false
51 changes: 49 additions & 2 deletions fuzz/fuzz_targets/parse-conditions.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,64 @@
#![no_main]
use libfuzzer_sys::fuzz_target;

use std::sync::Arc;
use std::collections::HashSet;
use clvmr::allocator::Allocator;
use chia::fuzzing_utils::{BitCursor, make_tree};
use chia::gen::conditions::parse_spends;
use chia::gen::conditions::{parse_conditions, ParseState, Spend, SpendBundleConditions};
use clvm_utils::tree_hash::tree_hash;
use chia_protocol::Bytes32;
use chia_protocol::Coin;

use chia::gen::flags::{COND_ARGS_NIL, STRICT_ARGS_COUNT, ENABLE_ASSERT_BEFORE, NO_UNKNOWN_CONDS};

fuzz_target!(|data: &[u8]| {
let mut a = Allocator::new();
let input = make_tree(&mut a, &mut BitCursor::new(data), false);

let mut ret = SpendBundleConditions {
spends: Vec::new(),
reserve_fee: 0,
height_absolute: 0,
seconds_absolute: 0,
before_height_absolute: None,
before_seconds_absolute: None,
agg_sig_unsafe: Vec::new(),
cost: 0,
};

let amount = 1337_u64;
let parent_id: Bytes32 = b"12345678901234567890123456789012".into();
let puzzle_hash = tree_hash(&a, input);
let coin_id = Arc::<Bytes32>::new(
Coin {
parent_coin_info: parent_id.into(),
puzzle_hash: puzzle_hash.into(),
amount,
}
.coin_id()
.into(),
);

let mut state = ParseState::new();

for flags in &[0, ENABLE_ASSERT_BEFORE | COND_ARGS_NIL, ENABLE_ASSERT_BEFORE | STRICT_ARGS_COUNT, NO_UNKNOWN_CONDS] {
let _ret = parse_spends(&a, input, 33000000000, *flags);

let 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"),
coin_id: coin_id.clone(),
height_relative: None,
seconds_relative: 0,
before_height_relative: None,
before_seconds_relative: None,
create_coin: HashSet::new(),
agg_sig_me: Vec::new(),
flags: 0_u32,
};
let mut max_cost: u64 = 3300000000;
let _ret = parse_conditions(&a, &mut ret, &mut state, coin_spend, input, *flags, &mut max_cost);
}
});

17 changes: 17 additions & 0 deletions fuzz/fuzz_targets/parse-spends.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#![no_main]
use libfuzzer_sys::fuzz_target;

use clvmr::allocator::Allocator;
use chia::fuzzing_utils::{BitCursor, make_tree};
use chia::gen::conditions::parse_spends;

use chia::gen::flags::{COND_ARGS_NIL, STRICT_ARGS_COUNT, ENABLE_ASSERT_BEFORE, NO_UNKNOWN_CONDS};

fuzz_target!(|data: &[u8]| {
let mut a = Allocator::new();
let input = make_tree(&mut a, &mut BitCursor::new(data), false);
for flags in &[0, ENABLE_ASSERT_BEFORE | COND_ARGS_NIL, ENABLE_ASSERT_BEFORE | STRICT_ARGS_COUNT, NO_UNKNOWN_CONDS] {
let _ret = parse_spends(&a, input, 33000000000, *flags);
}
});

43 changes: 32 additions & 11 deletions src/gen/conditions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -336,8 +336,15 @@ pub const ELIGIBLE_FOR_DEDUP: u32 = 1;
// These are all the conditions related directly to a specific spend.
#[derive(Debug)]
pub struct Spend {
pub coin_id: Arc<Bytes32>,
// the parent coin ID of the coin being spent
pub parent_id: NodePtr,
// the amount of the coin that's being spent
pub coin_amount: u64,
// the puzzle hash of the p
pub puzzle_hash: NodePtr,
// the coin ID of the coin being spent. This is computed from parent_id,
// coin_amount and puzzle_hash
pub coin_id: Arc<Bytes32>,
// conditions
// all these integers are initialized to 0, which also means "no
// constraint". i.e. a 0 in these conditions are inherently satisified and
Expand Down Expand Up @@ -393,7 +400,7 @@ pub struct SpendBundleConditions {
pub cost: u64,
}

struct ParseState {
pub struct ParseState {
// hashing of the announcements is deferred until parsing is complete. This
// means less work up-front, in case parsing/validation fails
announce_coin: HashSet<(Arc<Bytes32>, NodePtr)>,
Expand Down Expand Up @@ -431,7 +438,7 @@ struct ParseState {
}

impl ParseState {
fn new() -> ParseState {
pub fn new() -> ParseState {
ParseState {
announce_coin: HashSet::new(),
announce_puzzle: HashSet::new(),
Expand All @@ -447,7 +454,7 @@ impl ParseState {
}
}

fn parse_spend_conditions(
pub(crate) fn parse_single_spend(
ret: &mut SpendBundleConditions,
a: &Allocator,
state: &mut ParseState,
Expand All @@ -474,9 +481,11 @@ fn parse_spend_conditions(

state.removal_amount += my_amount as u128;

let mut spend = Spend {
coin_id,
let coin_spend = Spend {
parent_id,
coin_amount: my_amount,
puzzle_hash,
coin_id,
height_relative: None,
seconds_relative: 0,
before_height_relative: None,
Expand All @@ -487,7 +496,19 @@ fn parse_spend_conditions(
flags: ELIGIBLE_FOR_DEDUP,
};

let mut iter = first(a, cond)?;
let iter = first(a, cond)?;
parse_conditions(a, ret, state, coin_spend, iter, flags, max_cost)
}

pub fn parse_conditions(
a: &Allocator,
ret: &mut SpendBundleConditions,
state: &mut ParseState,
mut spend: Spend,
mut iter: NodePtr,
flags: u32,
max_cost: &mut Cost,
) -> Result<(), ValidationErr> {
while let Some((mut c, next)) = next(a, iter)? {
iter = next;
let op = match parse_opcode(a, first(a, c)?, flags) {
Expand Down Expand Up @@ -636,17 +657,17 @@ fn parse_spend_conditions(
}
}
Condition::AssertMyAmount(amount) => {
if amount != my_amount {
if amount != spend.coin_amount {
return Err(ValidationErr(c, ErrorCode::AssertMyAmountFailed));
}
}
Condition::AssertMyParentId(id) => {
if a.atom(id) != a.atom(parent_id) {
if a.atom(id) != a.atom(spend.parent_id) {
return Err(ValidationErr(c, ErrorCode::AssertMyParentIdFailed));
}
}
Condition::AssertMyPuzzlehash(hash) => {
if a.atom(hash) != a.atom(puzzle_hash) {
if a.atom(hash) != a.atom(spend.puzzle_hash) {
return Err(ValidationErr(c, ErrorCode::AssertMyPuzzlehashFailed));
}
}
Expand Down Expand Up @@ -716,7 +737,7 @@ pub fn parse_spends(
// as possible if cost is exceeded
// this function adds the spend to the passed-in ret
// as well as updates it with any conditions
parse_spend_conditions(&mut ret, a, &mut state, spend, flags, &mut cost_left)?;
parse_single_spend(&mut ret, a, &mut state, spend, flags, &mut cost_left)?;
}

if state.removal_amount < state.addition_amount {
Expand Down
1 change: 1 addition & 0 deletions src/gen/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@ pub mod flags;
pub mod get_puzzle_and_solution;
pub mod opcodes;
pub mod run_block_generator;
pub mod run_puzzle;
mod sanitize_int;
pub mod validation_error;
82 changes: 82 additions & 0 deletions src/gen/run_puzzle.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
use crate::gen::conditions::{
parse_conditions, ParseState, Spend, SpendBundleConditions, ELIGIBLE_FOR_DEDUP,
};
use crate::gen::validation_error::ValidationErr;
use chia_protocol::bytes::Bytes32;
use chia_protocol::coin::Coin;
use clvm_utils::tree_hash::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;
use std::collections::HashSet;
use std::sync::Arc;

pub fn run_puzzle(
a: &mut Allocator,
puzzle: &[u8],
solution: &[u8],
parent_id: &[u8],
amount: u64,
max_cost: u64,
flags: u32,
) -> Result<SpendBundleConditions, ValidationErr> {
let puzzle = node_from_bytes(a, puzzle)?;
let solution = node_from_bytes(a, solution)?;

let dialect = ChiaDialect::new(flags);
let Reduction(clvm_cost, conditions) = run_program(a, &dialect, puzzle, solution, max_cost)?;

let mut ret = SpendBundleConditions {
spends: Vec::new(),
reserve_fee: 0,
height_absolute: 0,
seconds_absolute: 0,
before_height_absolute: None,
before_seconds_absolute: None,
agg_sig_unsafe: Vec::new(),
cost: 0,
};
let mut state = ParseState::new();

let puzzle_hash = tree_hash(a, puzzle);
let coin_id = Arc::<Bytes32>::new(
Coin {
parent_coin_info: parent_id.into(),
puzzle_hash: puzzle_hash.into(),
amount,
}
.coin_id()
.into(),
);

let spend = Spend {
parent_id: a.new_atom(parent_id)?,
coin_amount: amount,
puzzle_hash: a.new_atom(&puzzle_hash)?,
coin_id,
height_relative: None,
seconds_relative: 0,
before_height_relative: None,
before_seconds_relative: None,
create_coin: HashSet::new(),
agg_sig_me: Vec::new(),
// assume it's eligible until we see an agg-sig condition
flags: ELIGIBLE_FOR_DEDUP,
};

let mut cost_left = max_cost - clvm_cost;

parse_conditions(
a,
&mut ret,
&mut state,
spend,
conditions,
flags,
&mut cost_left,
)?;
ret.cost = max_cost - cost_left;
Ok(ret)
}
12 changes: 12 additions & 0 deletions src/gen/validation_error.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
use clvmr::allocator::{Allocator, NodePtr, SExp};
use clvmr::reduction::EvalErr;

#[cfg(feature = "py-bindings")]
use pyo3::exceptions;
#[cfg(feature = "py-bindings")]
use pyo3::PyErr;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ErrorCode {
GeneratorRuntimeError,
Expand Down Expand Up @@ -57,6 +62,13 @@ impl From<std::io::Error> for ValidationErr {
}
}

#[cfg(feature = "py-bindings")]
impl std::convert::From<ValidationErr> for PyErr {
fn from(err: ValidationErr) -> PyErr {
exceptions::PyValueError::new_err(("ValidationError", u32::from(err.1)))
}
}

// helper functions that fail with ValidationErr
pub fn first(a: &Allocator, n: NodePtr) -> Result<NodePtr, ValidationErr> {
match a.sexp(n) {
Expand Down
Loading

0 comments on commit f3a4b0d

Please sign in to comment.