use chia::bls::Signature;
use chia::consensus::additions_and_removals::additions_and_removals;
use chia::consensus::consensus_constants::ConsensusConstants;
use chia::consensus::flags::DONT_VALIDATE_SIGNATURE;
use chia::consensus::get_puzzle_and_solution::get_puzzle_and_solution_for_coin;
use chia::consensus::run_block_generator::run_block_generator2;
use chia::consensus::{allocator::make_allocator, validation_error};
use chia::protocol::{Bytes32, FullBlock};
use clvmr::serde::{node_from_bytes_backrefs_record, node_to_bytes};
use crate::types::*;
pub fn block_additions_and_removals(
block: &FullBlock,
height: u32,
constants: &ConsensusConstants,
) -> Result<AdditionsAndRemovals, ChiaQueryError> {
let generator = match &block.transactions_generator {
Some(g) => g,
None => {
return Ok(AdditionsAndRemovals {
additions: reward_coins(block, height),
removals: Vec::new(),
});
}
};
let timestamp = block_timestamp(block);
let block_refs: Vec<&[u8]> = Vec::new();
let flags = DONT_VALIDATE_SIGNATURE;
let (raw_additions, raw_removals) =
additions_and_removals(generator.as_ref(), block_refs, flags, constants)
.map_err(|e| ChiaQueryError::PeerConnection(format!("CLVM execution failed: {e:?}")))?;
let mut additions: Vec<CoinRecord> = raw_additions
.iter()
.map(|(coin, _hint)| CoinRecord {
coin: Coin::from_protocol(coin),
confirmed_block_index: height,
spent_block_index: 0,
spent: false,
coinbase: false,
timestamp,
})
.collect();
additions.extend(reward_coins(block, height));
let removals: Vec<CoinRecord> = raw_removals
.iter()
.map(|coin| CoinRecord {
coin: Coin::from_protocol(coin),
confirmed_block_index: 0,
spent_block_index: height,
spent: true,
coinbase: false,
timestamp: 0,
})
.collect();
Ok(AdditionsAndRemovals {
additions,
removals,
})
}
pub fn block_spends(
block: &FullBlock,
constants: &ConsensusConstants,
) -> Result<Vec<CoinSpend>, ChiaQueryError> {
let generator = match &block.transactions_generator {
Some(g) => g,
None => return Ok(Vec::new()),
};
let flags = DONT_VALIDATE_SIGNATURE;
let block_refs: Vec<&[u8]> = Vec::new();
let mut allocator = make_allocator(flags);
let conds = run_block_generator2(
&mut allocator,
generator.as_ref(),
&block_refs,
constants.max_block_cost_clvm,
flags,
&Signature::default(),
None,
constants,
)
.map_err(|e| ChiaQueryError::PeerConnection(format!("run_block_generator2 failed: {e:?}")))?;
let (program, backrefs) =
node_from_bytes_backrefs_record(&mut allocator, generator.as_ref())
.map_err(|e| ChiaQueryError::PeerConnection(format!("parse generator: {e:?}")))?;
let args =
chia::consensus::run_block_generator::setup_generator_args(&mut allocator, &block_refs)
.map_err(|e| ChiaQueryError::PeerConnection(format!("setup args: {e:?}")))?;
let dialect = clvmr::chia_dialect::ChiaDialect::new(flags);
let reduction = clvmr::run_program::run_program(
&mut allocator,
&dialect,
program,
args,
constants.max_block_cost_clvm,
)
.map_err(|e| ChiaQueryError::PeerConnection(format!("run_program: {e:?}")))?;
let generator_output = reduction.1;
let mut spends = Vec::new();
for sc in &conds.spends {
let parent_id: Bytes32 = allocator.atom(sc.parent_id).as_ref().try_into().unwrap();
let puzzle_hash: Bytes32 = allocator.atom(sc.puzzle_hash).as_ref().try_into().unwrap();
let removal = chia::protocol::Coin {
parent_coin_info: parent_id,
puzzle_hash,
amount: sc.coin_amount,
};
if let Ok((puzzle_node, solution_node)) =
get_puzzle_and_solution_for_coin(&allocator, generator_output, &backrefs, &removal)
{
let puzzle_bytes = node_to_bytes(&allocator, puzzle_node).unwrap_or_default();
let solution_bytes = node_to_bytes(&allocator, solution_node).unwrap_or_default();
spends.push(CoinSpend {
coin: Coin::from_protocol(&removal),
puzzle_reveal: format!("0x{}", hex::encode(&puzzle_bytes)),
solution: format!("0x{}", hex::encode(&solution_bytes)),
});
}
}
Ok(spends)
}
pub fn block_spends_with_conditions(
block: &FullBlock,
constants: &ConsensusConstants,
) -> Result<Vec<CoinSpendWithConditions>, ChiaQueryError> {
let generator = match &block.transactions_generator {
Some(g) => g,
None => return Ok(Vec::new()),
};
let flags = DONT_VALIDATE_SIGNATURE;
let block_refs: Vec<&[u8]> = Vec::new();
let mut allocator = make_allocator(flags);
let conds = run_block_generator2(
&mut allocator,
generator.as_ref(),
&block_refs,
constants.max_block_cost_clvm,
flags,
&Signature::default(),
None,
constants,
)
.map_err(|e| ChiaQueryError::PeerConnection(format!("run_block_generator2 failed: {e:?}")))?;
let (program, backrefs) =
node_from_bytes_backrefs_record(&mut allocator, generator.as_ref())
.map_err(|e| ChiaQueryError::PeerConnection(format!("parse generator: {e:?}")))?;
let args =
chia::consensus::run_block_generator::setup_generator_args(&mut allocator, &block_refs)
.map_err(|e| ChiaQueryError::PeerConnection(format!("setup args: {e:?}")))?;
let dialect = clvmr::chia_dialect::ChiaDialect::new(flags);
let reduction = clvmr::run_program::run_program(
&mut allocator,
&dialect,
program,
args,
constants.max_block_cost_clvm,
)
.map_err(|e| ChiaQueryError::PeerConnection(format!("run_program: {e:?}")))?;
let generator_output = reduction.1;
let mut result = Vec::new();
for sc in &conds.spends {
let parent_id: Bytes32 = allocator.atom(sc.parent_id).as_ref().try_into().unwrap();
let puzzle_hash: Bytes32 = allocator.atom(sc.puzzle_hash).as_ref().try_into().unwrap();
let removal = chia::protocol::Coin {
parent_coin_info: parent_id,
puzzle_hash,
amount: sc.coin_amount,
};
if let Ok((puzzle_node, solution_node)) =
get_puzzle_and_solution_for_coin(&allocator, generator_output, &backrefs, &removal)
{
let puzzle_bytes = node_to_bytes(&allocator, puzzle_node).unwrap_or_default();
let solution_bytes = node_to_bytes(&allocator, solution_node).unwrap_or_default();
let conditions = match clvmr::run_program::run_program(
&mut allocator,
&dialect,
puzzle_node,
solution_node,
constants.max_block_cost_clvm,
) {
Ok(clvmr::reduction::Reduction(_, output)) => parse_conditions(&allocator, output),
Err(_) => Vec::new(),
};
result.push(CoinSpendWithConditions {
coin_spend: CoinSpend {
coin: Coin::from_protocol(&removal),
puzzle_reveal: format!("0x{}", hex::encode(&puzzle_bytes)),
solution: format!("0x{}", hex::encode(&solution_bytes)),
},
conditions,
});
}
}
Ok(result)
}
fn parse_conditions(allocator: &clvmr::Allocator, output: clvmr::NodePtr) -> Vec<Condition> {
parse_conditions_public(allocator, output)
}
pub fn parse_conditions_public(
allocator: &clvmr::Allocator,
mut output: clvmr::NodePtr,
) -> Vec<Condition> {
let mut conditions = Vec::new();
while let Ok(Some((cond, rest))) = validation_error::next(allocator, output) {
output = rest;
let Ok(opcode_node) = validation_error::first(allocator, cond) else {
continue;
};
let opcode_bytes = match allocator.sexp(opcode_node) {
clvmr::allocator::SExp::Atom => allocator.atom(opcode_node).as_ref().to_vec(),
clvmr::allocator::SExp::Pair(_, _) => continue,
};
let opcode = serde_json::Value::String(format!("0x{}", hex::encode(&opcode_bytes)));
let mut vars = Vec::new();
let Ok(mut args_iter) = validation_error::rest(allocator, cond) else {
continue;
};
while let Ok(Some((arg, rest))) = validation_error::next(allocator, args_iter) {
args_iter = rest;
let arg_hex = match allocator.sexp(arg) {
clvmr::allocator::SExp::Atom => {
format!("0x{}", hex::encode(allocator.atom(arg).as_ref()))
}
clvmr::allocator::SExp::Pair(_, _) => {
match clvmr::serde::node_to_bytes(allocator, arg) {
Ok(bytes) => format!("0x{}", hex::encode(&bytes)),
Err(_) => continue,
}
}
};
vars.push(arg_hex);
}
conditions.push(Condition { opcode, vars });
}
conditions
}
fn reward_coins(block: &FullBlock, height: u32) -> Vec<CoinRecord> {
let Some(ref ti) = block.transactions_info else {
return Vec::new();
};
let timestamp = block_timestamp(block);
ti.reward_claims_incorporated
.iter()
.map(|c| CoinRecord {
coin: Coin::from_protocol(c),
confirmed_block_index: height,
spent_block_index: 0,
spent: false,
coinbase: true,
timestamp,
})
.collect()
}
fn block_timestamp(block: &FullBlock) -> u64 {
block
.foliage_transaction_block
.as_ref()
.map(|ft| ft.timestamp)
.unwrap_or(0)
}