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

backwards compatible run_block_generator2() #262

Closed
wants to merge 5 commits into from
Closed
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
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 1 addition & 11 deletions chia-tools/src/bin/test-block-generators.rs
Original file line number Diff line number Diff line change
Expand Up @@ -244,17 +244,7 @@ fn main() {
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);
assert!(conditions.cost > 0);

// in order for the comparison below the hold, we need to
// patch up the cost of the rust generator to look like the
// baseline
conditions.cost = ti.cost;
} else {
assert_eq!(conditions.cost, ti.cost);
}
assert_eq!(conditions.cost, ti.cost);

if args.validate {
let mut baseline =
Expand Down
2 changes: 0 additions & 2 deletions clvm-utils/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,4 @@ repository = "https://github.com/Chia-Network/chia_rs/clvm-utils"
[dependencies]
clvmr = "0.3.0"
clvm-traits = { version = "0.1.0", path = "../clvm-traits" }

[dev-dependencies]
hex = "0.4.3"
7 changes: 7 additions & 0 deletions clvm-utils/fuzz/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ chia-fuzz = { path = "../../fuzz" }
clvm-utils = { path = ".." }
clvm-traits = { path = "../../clvm-traits" }
chia = { path = "../.." }
hex = "0.4.3"

[[bin]]
name = "tree-hash"
Expand All @@ -29,3 +30,9 @@ path = "fuzz_targets/curry.rs"
test = false
doc = false
bench = false

[[bin]]
name = "tree-hash-with-cost"
path = "fuzz_targets/tree-hash-with-cost.rs"
test = false
doc = false
13 changes: 13 additions & 0 deletions clvm-utils/fuzz/fuzz_targets/tree-hash-with-cost.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#![no_main]
#[cfg(fuzzing)]
use clvm_utils::cmp_hash;
use clvmr::Allocator;
use fuzzing_utils::{make_tree, BitCursor};
use libfuzzer_sys::fuzz_target;

fuzz_target!(|data: &[u8]| {
let mut a = Allocator::new();
let input = make_tree(&mut a, &mut BitCursor::new(data), false);
#[cfg(fuzzing)]
cmp_hash(&mut a, input);
});
217 changes: 217 additions & 0 deletions clvm-utils/src/tree_hash.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,228 @@
use clvmr::allocator::{Allocator, NodePtr, SExp};
use clvmr::cost::Cost;
use clvmr::sha2::{Digest, Sha256};

enum TreeOp {
SExp(NodePtr),
Cons,
}

/*
op_cost: 1
op_cost: 1
op_cost: 1
traverse: 44
traverse: {traverse_cost}
cons_cost: 50
traverse: 48
cons_cost: 50
traverse: {traverse_cost2}
apply_cost: 90
op_cost: 1
traverse: 44
op_cost: 1
quote_cost: 20
quote_cost: 20
op_cost: 1
traverse: 52
listp_cost: 19
if_cost: 33
apply_cost: 90
op_cost: 1
*/
const HASH_TRAVERSE_COST: Cost = 567;

fn subtract_cost(cost_left: &mut Cost, subtract: Cost) -> Option<()> {
if subtract > *cost_left {
None
} else {
*cost_left -= subtract;
Some(())
}
}

// TODO: simulate object allocation counter, to correctly implement
// LIMIT_OBJECTS restriction

// TODO: simulate CLVM stack depth to correctly implement the LIMIT_STACK
// restriction

// The traverse cost seem to depend on the environment of the caller. The base
// cost is 40 and then 4 for every bit we parse.
// When calling sha256tree directly from the `mod`, it's 48.
// When calling it from the ROM generator, it's 60
pub fn tree_hash_with_cost(
a: &Allocator,
node: NodePtr,
traverse_cost2: Cost,
cost_left: &mut Cost,
) -> Option<[u8; 32]> {
let mut hashes: Vec<[u8; 32]> = vec![];
let mut ops = vec![TreeOp::SExp(node)];

let mut traverse_cost = 60;

const SHA256_BASE_COST: Cost = 87;
const SHA256_COST_PER_ARG: Cost = 134;
const SHA256_COST_PER_BYTE: Cost = 2;
const MALLOC_COST_PER_BYTE: Cost = 10;

while let Some(op) = ops.pop() {
match op {
TreeOp::SExp(node) => {
subtract_cost(
cost_left,
HASH_TRAVERSE_COST + traverse_cost + traverse_cost2,
)?;
traverse_cost = 56;

match a.sexp(node) {
SExp::Atom => {
// traverse: 52
// quote_cost: 20
subtract_cost(cost_left, 72)?;
let mut sha256 = Sha256::new();
sha256.update([1_u8]);
let buf = a.atom(node);
sha256.update(buf);
hashes.push(sha256.finalize().into());
let mut sha_cost = SHA256_BASE_COST;
sha_cost += 2 * SHA256_COST_PER_ARG;
sha_cost += (1 + buf.len() as Cost) * SHA256_COST_PER_BYTE;
// sha256_cost: ...
// malloc_cost: 320
subtract_cost(cost_left, sha_cost + 32 * MALLOC_COST_PER_BYTE)?;
}
SExp::Pair(left, right) => {
ops.push(TreeOp::Cons);
ops.push(TreeOp::SExp(left));
ops.push(TreeOp::SExp(right));
}
}
}
TreeOp::Cons => {
// quote_cost: 20
subtract_cost(cost_left, 20)?;

let mut sha256 = Sha256::new();
sha256.update([2_u8]);
sha256.update(hashes.pop().unwrap());
sha256.update(hashes.pop().unwrap());
hashes.push(sha256.finalize().into());

const SHA_COST: Cost = SHA256_BASE_COST;
// sha256_cost: 619
// malloc_cost: 320
subtract_cost(
cost_left,
3 * SHA256_COST_PER_ARG
+ (1 + 32 + 32) * SHA256_COST_PER_BYTE
+ SHA_COST
+ 32 * MALLOC_COST_PER_BYTE,
)?;
}
}
}

assert!(hashes.len() == 1);
Some(hashes[0])
}

#[cfg(any(test, fuzzing))]
pub fn cmp_hash(a: &mut Allocator, root: NodePtr) {
use clvmr::chia_dialect::ChiaDialect;
use clvmr::reduction::Reduction;
use clvmr::run_program::run_program;
use clvmr::serde::node_from_bytes;

/*
This is the compiled version of:
(mod (TREE)
(defun sha256tree (TREE)
(if (l TREE)
(sha256 2 (sha256tree (f TREE)) (sha256tree (r TREE)))
(sha256 1 TREE)
)
)
(sha256tree TREE)
)

CLVM:

(a (q 2 2 (c 2 (c 5 ())))
(c (q 2 (i (l 5)
(q #sha256 (q . 2)
(a 2 (c 2 (c 9 ())))
(a 2 (c 2 (c 13 ())))
)
(q #sha256 (q . 1) 5)
) 1) 1)
)
*/
let tree_hash_clvm: Vec<u8> = hex::decode(
"\
ff02ffff01ff02ff02ffff04ff02ffff04ff05ff80808080ffff04ffff01ff02\
ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff02ffff04ff02ffff04ff\
09ff80808080ffff02ff02ffff04ff02ffff04ff0dff8080808080ffff01ff0b\
ffff0101ff058080ff0180ff018080",
)
.expect("hex::decode");

let program = node_from_bytes(a, &tree_hash_clvm).expect("node_from_bytes");

let argument = a.new_pair(root, a.null()).unwrap();

let dialect = ChiaDialect::new(0);
let Reduction(cost1, ret1) =
run_program(a, &dialect, program, argument, 11000000000).expect("run_program");

// 226 is the overhead of starting the program and calling into the mod
// When running it directly in the mod scope, the first iteration
// environment lookups (path traversal) needs 2 fewer bits, and so is 8 cost
// cheaper. To make the cost match, we therefore deduct 8
let mut expect_cost = cost1 - 226 + 8;
let ret2 = tree_hash_with_cost(a, root, 48, &mut expect_cost).unwrap();

assert_eq!(a.atom(ret1), ret2);
println!("clvm cost: {cost1} rust cost: {}", cost1 - expect_cost);
assert_eq!(expect_cost, 0);
}

#[test]
fn test_tree_hash_cost() {
use clvmr::Allocator;

let mut a = Allocator::new();

let atom3 = a.new_atom(&[1, 2, 3]).unwrap();
let atom2 = a.new_atom(&[4, 5]).unwrap();
let atom1 = a.new_atom(&[6]).unwrap();
let atom_c = a.new_atom(&[0xcc, 0xcc]).unwrap();
let atom_a = a.new_atom(&[0xaa, 0xaa]).unwrap();

cmp_hash(&mut a, atom3);

let root1 = a.new_pair(atom1, atom1).unwrap();
let root2 = a.new_pair(atom1, atom1).unwrap();
let root = a.new_pair(root1, root2).unwrap();
cmp_hash(&mut a, root);

let root = a.new_pair(atom1, atom2).unwrap();
cmp_hash(&mut a, root);

let root = a.new_pair(atom2, atom3).unwrap();
cmp_hash(&mut a, root);

let root = a.new_pair(atom1, atom3).unwrap();
cmp_hash(&mut a, root);

let root = a.new_pair(atom2, root).unwrap();
cmp_hash(&mut a, root);

let root = a.new_pair(atom_c, atom_a).unwrap();
cmp_hash(&mut a, root);
}

pub fn tree_hash_atom(bytes: &[u8]) -> [u8; 32] {
let mut sha256 = Sha256::new();
sha256.update([1]);
Expand Down
8 changes: 1 addition & 7 deletions fuzz/fuzz_targets/run-generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
use chia::allocator::make_allocator;
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};
use clvmr::chia_dialect::LIMIT_HEAP;
use libfuzzer_sys::fuzz_target;

Expand All @@ -16,18 +15,13 @@ fuzz_target!(|data: &[u8]| {
drop(a2);

match (r1, r2) {
(Err(ValidationErr(_, ErrorCode::CostExceeded)), Ok(_)) => {
// Since run_block_generator2 cost less, it's not a problem if the
// original generator runs our of cost while the rust implementation
// succeeds. This is part of its features.
}
(Err(_), Err(_)) => {
// The specific error may not match, because
// run_block_generator2() parses conditions after each spend
// instead of after running all spends
}
(Ok(a), Ok(b)) => {
assert!(a.cost >= b.cost);
assert_eq!(a.cost, b.cost);
assert_eq!(a.reserve_fee, b.reserve_fee);
assert_eq!(a.removal_amount, b.removal_amount);
assert_eq!(a.addition_amount, b.addition_amount);
Expand Down
Loading
Loading