use crate::allocator::make_allocator;
use crate::condition_sanitizers::parse_amount;
use crate::conditions::{
EmptyVisitor, MAX_SPENDS_PER_BLOCK, ParseState, SpendBundleConditions, parse_spends,
process_single_spend, validate_conditions, validate_signature,
};
use crate::consensus_constants::ConsensusConstants;
use crate::flags::ConsensusFlags;
use crate::opcodes::{
AGG_SIG_AMOUNT, AGG_SIG_ME, AGG_SIG_PARENT, AGG_SIG_PARENT_AMOUNT, AGG_SIG_PARENT_PUZZLE,
AGG_SIG_PUZZLE, AGG_SIG_PUZZLE_AMOUNT, AGG_SIG_UNSAFE, CREATE_COIN,
};
use crate::validation_error::{ErrorCode, ValidationErr, first};
use chia_bls::{BlsCache, Signature};
use chia_protocol::{BytesImpl, Coin, CoinSpend, Program};
use chia_puzzles::{CHIALISP_DESERIALISATION, ROM_BOOTSTRAP_GENERATOR};
use clvm_traits::FromClvm;
use clvm_traits::MatchByte;
use clvm_utils::{TreeCache, tree_hash_cached};
use clvmr::SExp;
use clvmr::allocator::{Allocator, NodePtr};
use clvmr::chia_dialect::ChiaDialect;
use clvmr::cost::Cost;
use clvmr::reduction::Reduction;
use clvmr::run_program::run_program;
use clvmr::serde::{node_from_bytes, node_from_bytes_backrefs};
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 {
*cost_left -= subtract;
Ok(())
}
}
pub fn setup_generator_args<GenBuf: AsRef<[u8]>, I: IntoIterator<Item = GenBuf>>(
a: &mut Allocator,
block_refs: I,
flags: ConsensusFlags,
) -> Result<NodePtr, ValidationErr>
where
<I as IntoIterator>::IntoIter: DoubleEndedIterator,
{
if flags.contains(ConsensusFlags::SIMPLE_GENERATOR) {
if block_refs.into_iter().next().is_some() {
return Err(ValidationErr(a.nil(), ErrorCode::TooManyGeneratorRefs));
}
return Ok(a.nil());
}
let clvm_deserializer = node_from_bytes(a, &CHIALISP_DESERIALISATION)?;
let mut blocks = NodePtr::NIL;
for g in block_refs.into_iter().rev() {
let ref_gen = a.new_atom(g.as_ref())?;
blocks = a.new_pair(ref_gen, blocks)?;
}
let args = a.new_pair(blocks, NodePtr::NIL)?;
Ok(a.new_pair(clvm_deserializer, args)?)
}
#[allow(clippy::too_many_arguments)]
pub fn run_block_generator<GenBuf: AsRef<[u8]>, I: IntoIterator<Item = GenBuf>>(
program: &[u8],
block_refs: I,
max_cost: u64,
flags: ConsensusFlags,
signature: &Signature,
bls_cache: Option<&BlsCache>,
constants: &ConsensusConstants,
) -> Result<(Allocator, SpendBundleConditions), ValidationErr>
where
<I as IntoIterator>::IntoIter: DoubleEndedIterator,
{
check_generator_quote(program, flags)?;
let mut a = make_allocator(flags);
let mut cost_left = max_cost;
let byte_cost = program.len() as u64 * constants.cost_per_byte;
subtract_cost(&a, &mut cost_left, byte_cost)?;
let rom_generator = node_from_bytes(&mut a, &ROM_BOOTSTRAP_GENERATOR)?;
let program = node_from_bytes_backrefs(&mut a, program)?;
check_generator_node(&a, program, flags)?;
let mut args = a.nil();
for g in block_refs.into_iter().rev() {
let ref_gen = a.new_atom(g.as_ref())?;
args = a.new_pair(ref_gen, args)?;
}
args = a.new_pair(args, a.nil())?;
let args = a.new_pair(args, a.nil())?;
let args = a.new_pair(program, args)?;
let dialect = ChiaDialect::new(flags.to_clvm_flags());
let Reduction(clvm_cost, generator_output) =
run_program(&mut a, &dialect, rom_generator, args, cost_left)?;
subtract_cost(&a, &mut cost_left, clvm_cost)?;
let mut result = parse_spends::<EmptyVisitor>(
&a,
generator_output,
cost_left,
0, flags,
signature,
bls_cache,
constants,
)?;
result.cost += max_cost - cost_left;
result.execution_cost = clvm_cost;
Ok((a, result))
}
fn extract_n<const N: usize>(
a: &Allocator,
mut n: NodePtr,
e: ErrorCode,
) -> Result<[NodePtr; N], ValidationErr> {
let mut ret: [NodePtr; N] = [NodePtr::NIL; N];
let mut counter = 0;
assert!(N > 0);
while let Some((item, rest)) = a.next(n) {
if counter == N - 1 {
break;
}
n = rest;
ret[counter] = item;
counter += 1;
}
if counter != N - 1 {
return Err(ValidationErr(n, e));
}
ret[counter] = n;
Ok(ret)
}
#[inline]
pub fn check_generator_quote(program: &[u8], flags: ConsensusFlags) -> Result<(), ValidationErr> {
if !flags.contains(ConsensusFlags::SIMPLE_GENERATOR) || program.starts_with(&[0xff, 0x01]) {
Ok(())
} else {
Err(ValidationErr(
NodePtr::NIL,
ErrorCode::ComplexGeneratorReceived,
))
}
}
#[inline]
pub fn check_generator_node(
a: &Allocator,
program: NodePtr,
flags: ConsensusFlags,
) -> Result<(), ValidationErr> {
if !flags.contains(ConsensusFlags::SIMPLE_GENERATOR) {
return Ok(());
}
match <(MatchByte<1>, NodePtr)>::from_clvm(a, program) {
Err(..) => Err(ValidationErr(
NodePtr::NIL,
ErrorCode::ComplexGeneratorReceived,
)),
_ => Ok(()),
}
}
#[allow(clippy::too_many_arguments)]
pub fn run_block_generator2<GenBuf: AsRef<[u8]>, I: IntoIterator<Item = GenBuf>>(
program: &[u8],
block_refs: I,
max_cost: u64,
flags: ConsensusFlags,
signature: &Signature,
bls_cache: Option<&BlsCache>,
constants: &ConsensusConstants,
) -> Result<(Allocator, SpendBundleConditions), ValidationErr>
where
<I as IntoIterator>::IntoIter: DoubleEndedIterator,
{
check_generator_quote(program, flags)?;
let mut a = make_allocator(flags);
let byte_cost = program.len() as u64 * constants.cost_per_byte;
let mut cost_left = max_cost;
subtract_cost(&a, &mut cost_left, byte_cost)?;
let program = node_from_bytes_backrefs(&mut a, program)?;
check_generator_node(&a, program, flags)?;
let args = setup_generator_args(&mut a, block_refs, flags)?;
let dialect = ChiaDialect::new(flags.to_clvm_flags());
let Reduction(clvm_cost, all_spends) = run_program(&mut a, &dialect, program, args, cost_left)?;
subtract_cost(&a, &mut cost_left, clvm_cost)?;
let mut ret = SpendBundleConditions::default();
let all_spends = first(&a, all_spends)?;
ret.execution_cost += clvm_cost;
let mut state = ParseState::default();
let mut cache = TreeCache::default();
let mut iter = all_spends;
while let Some((spend, rest)) = a.next(iter) {
iter = rest;
let [_, puzzle, _] = extract_n::<3>(&a, spend, ErrorCode::InvalidCondition)?;
cache.visit_tree(&a, puzzle);
}
let mut spends_left: usize = if flags.contains(ConsensusFlags::LIMIT_SPENDS) {
MAX_SPENDS_PER_BLOCK
} else {
usize::MAX
};
let mut iter = all_spends;
while let Some((spend, rest)) = a.next(iter) {
iter = rest;
if spends_left == 0 {
return Err(ValidationErr(spend, ErrorCode::TooManySpends));
}
spends_left -= 1;
let [parent_id, puzzle, amount, solution, _spend_level_extra] =
extract_n::<5>(&a, spend, ErrorCode::InvalidCondition)?;
let Reduction(clvm_cost, conditions) =
run_program(&mut a, &dialect, puzzle, solution, cost_left)?;
subtract_cost(&a, &mut cost_left, clvm_cost)?;
ret.execution_cost += clvm_cost;
let buf = tree_hash_cached(&a, puzzle, &mut cache);
let puzzle_hash = a.new_atom(&buf)?;
process_single_spend::<EmptyVisitor>(
&a,
&mut ret,
&mut state,
parent_id,
puzzle_hash,
amount,
conditions,
flags,
&mut cost_left,
clvm_cost,
constants,
)?;
}
if a.atom_len(iter) != 0 {
return Err(ValidationErr(iter, ErrorCode::GeneratorRuntimeError));
}
validate_conditions(&a, &ret, &state, a.nil(), flags)?;
validate_signature(&state, signature, flags, bls_cache)?;
ret.validated_signature = !flags.contains(ConsensusFlags::DONT_VALIDATE_SIGNATURE);
ret.cost = max_cost - cost_left;
Ok((a, ret))
}
pub fn get_coinspends_for_trusted_block<GenBuf: AsRef<[u8]>, I: IntoIterator<Item = GenBuf>>(
constants: &ConsensusConstants,
generator: &Program,
refs: I,
flags: ConsensusFlags,
) -> Result<Vec<CoinSpend>, ValidationErr>
where
<I as IntoIterator>::IntoIter: DoubleEndedIterator,
{
let mut a = make_allocator(flags);
check_generator_quote(generator.as_ref(), flags)?;
let mut output = Vec::<CoinSpend>::new();
let program = node_from_bytes_backrefs(&mut a, generator)?;
check_generator_node(&a, program, flags)?;
let args = setup_generator_args(&mut a, refs, flags)?;
let dialect = ChiaDialect::new(flags.to_clvm_flags());
let Reduction(_clvm_cost, res) = run_program(
&mut a,
&dialect,
program,
args,
constants.max_block_cost_clvm,
)?;
let (first, _rest) = a
.next(res)
.ok_or(ValidationErr(res, ErrorCode::GeneratorRuntimeError))?;
let mut cache = TreeCache::default();
let mut iter = first;
while let Some((spend, rest)) = a.next(iter) {
iter = rest;
let Ok([_, puzzle, _]) = extract_n::<3>(&a, spend, ErrorCode::InvalidCondition) else {
continue;
};
cache.visit_tree(&a, puzzle);
}
iter = first;
while let Some((spend, rest)) = a.next(iter) {
iter = rest;
let Ok([parent_id, puzzle, amount, solution, _spend_level_extra]) =
extract_n::<5>(&a, spend, ErrorCode::InvalidCondition)
else {
continue; };
let puzhash = tree_hash_cached(&a, puzzle, &mut cache);
let parent_id = BytesImpl::<32>::from_clvm(&a, parent_id)
.map_err(|_| ValidationErr(first, ErrorCode::InvalidParentId))?;
let coin = Coin::new(
parent_id,
puzhash.into(),
parse_amount(&a, amount, ErrorCode::InvalidCoinAmount)?,
);
let puzzle_program = Program::from_clvm(&a, puzzle).unwrap_or_default();
let solution_program = Program::from_clvm(&a, solution).unwrap_or_default();
let coinspend = CoinSpend::new(coin, puzzle_program, solution_program);
output.push(coinspend);
}
Ok(output)
}
const MAX_CONDITIONS_PER_SPEND: usize = 1024;
fn is_high_priority_condition(op: u32) -> bool {
u16::try_from(op).is_ok()
&& matches!(
op as u16,
AGG_SIG_PARENT
| AGG_SIG_PUZZLE
| AGG_SIG_AMOUNT
| AGG_SIG_PUZZLE_AMOUNT
| AGG_SIG_PARENT_AMOUNT
| AGG_SIG_PARENT_PUZZLE
| AGG_SIG_UNSAFE
| AGG_SIG_ME
| CREATE_COIN
)
}
#[allow(clippy::type_complexity)]
pub fn get_coinspends_with_conditions_for_trusted_block<
GenBuf: AsRef<[u8]>,
I: IntoIterator<Item = GenBuf>,
>(
constants: &ConsensusConstants,
generator: &Program,
refs: I,
flags: ConsensusFlags,
) -> Result<Vec<(CoinSpend, Vec<(u32, Vec<Vec<u8>>)>)>, ValidationErr>
where
<I as IntoIterator>::IntoIter: DoubleEndedIterator,
{
let mut a = make_allocator(flags);
check_generator_quote(generator.as_ref(), flags)?;
let mut output = Vec::<(CoinSpend, Vec<(u32, Vec<Vec<u8>>)>)>::new();
let program = node_from_bytes_backrefs(&mut a, generator)?;
check_generator_node(&a, program, flags)?;
let args = setup_generator_args(&mut a, refs, flags)?;
let dialect = ChiaDialect::new(flags.to_clvm_flags());
let Reduction(_clvm_cost, res) = run_program(
&mut a,
&dialect,
program,
args,
constants.max_block_cost_clvm,
)
.map_err(|_| ValidationErr(program, ErrorCode::GeneratorRuntimeError))?;
let (first, _rest) = a
.next(res)
.ok_or(ValidationErr(res, ErrorCode::GeneratorRuntimeError))?;
let mut cache = TreeCache::default();
let mut iter = first;
while let Some((spend, rest)) = a.next(iter) {
iter = rest;
let [_, puzzle, _] = extract_n::<3>(&a, spend, ErrorCode::InvalidCondition)?;
cache.visit_tree(&a, puzzle);
}
iter = first;
while let Some((spend, rest)) = a.next(iter) {
iter = rest;
let mut cond_output = Vec::<(u32, Vec<Vec<u8>>)>::new();
let Ok([parent_id, puzzle, amount, solution, _spend_level_extra]) =
extract_n::<5>(&a, spend, ErrorCode::InvalidCondition)
else {
continue; };
let puzhash = tree_hash_cached(&a, puzzle, &mut cache);
let parent_id = BytesImpl::<32>::from_clvm(&a, parent_id)
.map_err(|_| ValidationErr(first, ErrorCode::InvalidParentId))?;
let coin = Coin::new(
parent_id,
puzhash.into(),
parse_amount(&a, amount, ErrorCode::InvalidCoinAmount)?,
);
let puzzle_program = Program::from_clvm(&a, puzzle).unwrap_or_default();
let solution_program = Program::from_clvm(&a, solution).unwrap_or_default();
let Reduction(_clvm_cost, res) = run_program(
&mut a,
&dialect,
puzzle,
solution,
constants.max_block_cost_clvm,
)
.map_err(|_| ValidationErr(program, ErrorCode::GeneratorRuntimeError))?;
let mut iter_two = res;
'outer: while let Some((condition, rest_two)) = a.next(iter_two) {
iter_two = rest_two;
let mut iter_three = condition;
let Some((condition_values, rest_three)) = a.next(iter_three) else {
continue;
};
iter_three = rest_three;
let Some(opcode) = a.small_number(condition_values) else {
continue;
};
let mut bytes_vec = Vec::<Vec<u8>>::new();
'inner: while let Some((condition_values, rest_three)) = a.next(iter_three) {
iter_three = rest_three;
if bytes_vec.len() < 6 {
if let SExp::Atom = a.sexp(condition_values) {
if a.atom_len(condition_values) >= 1024 {
continue 'outer;
}
let bytes = a.atom(condition_values).to_vec();
bytes_vec.push(bytes);
}
} else {
break 'inner; }
}
if cond_output.len() >= MAX_CONDITIONS_PER_SPEND && !is_high_priority_condition(opcode)
{
continue 'outer;
}
cond_output.push((opcode, bytes_vec));
}
output.push((
CoinSpend::new(coin, puzzle_program, solution_program),
cond_output,
));
}
Ok(output)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::conditions::MAX_SPENDS_PER_BLOCK;
use crate::consensus_constants::TEST_CONSTANTS;
use crate::opcodes::{CREATE_COIN, CREATE_COIN_COST, NEW_CREATE_COIN_COST, SPEND_COST};
use crate::solution_generator::solution_generator;
use chia_protocol::Bytes32;
use clvm_traits::ToClvm;
use clvm_utils::tree_hash_atom;
use clvmr::serde::node_to_bytes;
use rstest::rstest;
const IDENTITY_PUZZLE: &[u8] = &[1];
fn make_generator(num_spends: usize) -> Vec<u8> {
let puzzle_hash = tree_hash_atom(&[1]).to_bytes();
let empty_solution: &[u8] = &[0x80];
let spends = (0..num_spends).map(|i| {
let mut parent = [0u8; 32];
parent[0..4].copy_from_slice(&(i as u32).to_be_bytes());
(
Coin::new(parent.into(), puzzle_hash.into(), 0),
IDENTITY_PUZZLE,
empty_solution,
)
});
solution_generator(spends).expect("solution_generator")
}
fn make_generator_with_create_coins(num_spends: usize, coins_per_spend: usize) -> Vec<u8> {
let puzzle_hash = Bytes32::from(tree_hash_atom(&[1]).to_bytes());
let mut a = Allocator::new();
let mut conds = a.nil();
for i in 0..coins_per_spend {
let cond = (CREATE_COIN, (puzzle_hash, (i as u64, 0)))
.to_clvm(&mut a)
.unwrap();
conds = a.new_pair(cond, conds).unwrap();
}
let solution_bytes = node_to_bytes(&a, conds).unwrap();
let total_amount: u64 = (0..coins_per_spend as u64).sum();
let spends = (0..num_spends).map(|i| {
let mut parent = [0u8; 32];
parent[0..4].copy_from_slice(&(i as u32).to_be_bytes());
(
Coin::new(parent.into(), puzzle_hash, total_amount),
IDENTITY_PUZZLE,
solution_bytes.as_slice(),
)
});
solution_generator(spends).expect("solution_generator")
}
#[rstest]
#[case(MAX_SPENDS_PER_BLOCK, ConsensusFlags::LIMIT_SPENDS, None)]
#[case(MAX_SPENDS_PER_BLOCK + 1, ConsensusFlags::LIMIT_SPENDS, Some(ErrorCode::TooManySpends))]
#[case(MAX_SPENDS_PER_BLOCK + 1, ConsensusFlags::empty(), None)]
fn test_limit_spends_run_block_generator2(
#[case] num_spends: usize,
#[case] extra_flags: ConsensusFlags,
#[case] expected_err: Option<ErrorCode>,
) {
let program = make_generator(num_spends);
let flags = extra_flags | ConsensusFlags::DONT_VALIDATE_SIGNATURE;
let blocks: &[&[u8]] = &[];
let result = run_block_generator2(
&program,
blocks,
u64::MAX,
flags,
&Signature::default(),
None,
&TEST_CONSTANTS,
);
match (expected_err, result) {
(Some(err), Err(e)) => {
assert_eq!(e.1, err);
}
(None, Ok(conds)) => {
assert_eq!(conds.1.spends.len(), num_spends);
}
_ => {
panic!("mismatch");
}
}
}
#[rstest]
#[case(1, 1)]
#[case(3, 1)]
#[case(1, 3)]
#[case(5, 5)]
fn test_cost_conditions_with_create_coin(
#[case] num_spends: usize,
#[case] coins_per_spend: usize,
) {
let program = make_generator_with_create_coins(num_spends, coins_per_spend);
let blocks: &[&[u8]] = &[];
let num_coins = (num_spends * coins_per_spend) as u64;
let (_, without) = run_block_generator2(
&program,
blocks,
u64::MAX,
ConsensusFlags::DONT_VALIDATE_SIGNATURE,
&Signature::default(),
None,
&TEST_CONSTANTS,
)
.expect("without COST_CONDITIONS");
let (_, with) = run_block_generator2(
&program,
blocks,
u64::MAX,
ConsensusFlags::DONT_VALIDATE_SIGNATURE | ConsensusFlags::COST_CONDITIONS,
&Signature::default(),
None,
&TEST_CONSTANTS,
)
.expect("with COST_CONDITIONS");
assert_eq!(without.spends.len(), num_spends);
assert_eq!(with.spends.len(), num_spends);
assert_eq!(without.condition_cost, CREATE_COIN_COST * num_coins);
assert_eq!(
with.condition_cost,
SPEND_COST * num_spends as u64 + NEW_CREATE_COIN_COST * num_coins
);
assert_eq!(without.execution_cost, with.execution_cost);
}
}