1use chia_bls::Signature;
8use chia_consensus::additions_and_removals::additions_and_removals;
9use chia_consensus::consensus_constants::ConsensusConstants;
10use chia_consensus::flags::DONT_VALIDATE_SIGNATURE;
11use chia_consensus::get_puzzle_and_solution::get_puzzle_and_solution_for_coin;
12use chia_consensus::run_block_generator::run_block_generator2;
13use chia_consensus::{allocator::make_allocator, validation_error};
14use chia_protocol::{Bytes32, FullBlock};
15
16use clvmr::serde::{node_from_bytes_backrefs, node_to_bytes};
17
18use crate::types::*;
19
20pub fn block_additions_and_removals(
26 block: &FullBlock,
27 height: u32,
28 constants: &ConsensusConstants,
29) -> Result<AdditionsAndRemovals, ChiaQueryError> {
30 let generator = match &block.transactions_generator {
31 Some(g) => g,
32 None => {
33 return Ok(AdditionsAndRemovals {
34 additions: reward_coins(block, height),
35 removals: Vec::new(),
36 });
37 }
38 };
39
40 let timestamp = block_timestamp(block);
41 let block_refs: Vec<&[u8]> = Vec::new();
42 let flags = DONT_VALIDATE_SIGNATURE;
43
44 let (raw_additions, raw_removals) =
45 additions_and_removals(generator.as_ref(), block_refs, flags, constants)
46 .map_err(|e| ChiaQueryError::PeerConnection(format!("CLVM execution failed: {e:?}")))?;
47
48 let mut additions: Vec<CoinRecord> = raw_additions
49 .iter()
50 .map(|(coin, _hint)| CoinRecord {
51 coin: Coin::from_protocol(coin),
52 confirmed_block_index: height,
53 spent_block_index: 0,
54 spent: false,
55 coinbase: false,
56 timestamp,
57 })
58 .collect();
59
60 additions.extend(reward_coins(block, height));
61
62 let removals: Vec<CoinRecord> = raw_removals
66 .iter()
67 .map(|(_coin_id, coin)| CoinRecord {
68 coin: Coin::from_protocol(coin),
69 confirmed_block_index: 0,
70 spent_block_index: height,
71 spent: true,
72 coinbase: false,
73 timestamp: 0,
74 })
75 .collect();
76
77 Ok(AdditionsAndRemovals {
78 additions,
79 removals,
80 })
81}
82
83pub fn block_spends(
90 block: &FullBlock,
91 constants: &ConsensusConstants,
92) -> Result<Vec<CoinSpend>, ChiaQueryError> {
93 let generator = match &block.transactions_generator {
94 Some(g) => g,
95 None => return Ok(Vec::new()),
96 };
97
98 let flags = DONT_VALIDATE_SIGNATURE;
99 let block_refs: Vec<&[u8]> = Vec::new();
100 let mut allocator = make_allocator(flags);
101
102 let conds = run_block_generator2(
103 &mut allocator,
104 generator.as_ref(),
105 &block_refs,
106 constants.max_block_cost_clvm,
107 flags,
108 &Signature::default(),
109 None,
110 constants,
111 )
112 .map_err(|e| ChiaQueryError::PeerConnection(format!("run_block_generator2 failed: {e:?}")))?;
113
114 let program = node_from_bytes_backrefs(&mut allocator, generator.as_ref())
115 .map_err(|e| ChiaQueryError::PeerConnection(format!("parse generator: {e:?}")))?;
116
117 let args = chia_consensus::run_block_generator::setup_generator_args(
118 &mut allocator,
119 &block_refs,
120 flags,
121 )
122 .map_err(|e| ChiaQueryError::PeerConnection(format!("setup args: {e:?}")))?;
123
124 let dialect = clvmr::chia_dialect::ChiaDialect::new(flags);
125 let reduction = clvmr::run_program::run_program(
126 &mut allocator,
127 &dialect,
128 program,
129 args,
130 constants.max_block_cost_clvm,
131 )
132 .map_err(|e| ChiaQueryError::PeerConnection(format!("run_program: {e:?}")))?;
133
134 let generator_output = reduction.1;
135
136 let mut spends = Vec::new();
137 for sc in &conds.spends {
138 let parent_id: Bytes32 = allocator.atom(sc.parent_id).as_ref().try_into().unwrap();
139 let puzzle_hash: Bytes32 = allocator.atom(sc.puzzle_hash).as_ref().try_into().unwrap();
140 let removal = chia_protocol::Coin {
141 parent_coin_info: parent_id,
142 puzzle_hash,
143 amount: sc.coin_amount,
144 };
145
146 if let Ok((puzzle_node, solution_node)) =
147 get_puzzle_and_solution_for_coin(&allocator, generator_output, &removal)
148 {
149 let puzzle_bytes = node_to_bytes(&allocator, puzzle_node).unwrap_or_default();
150 let solution_bytes = node_to_bytes(&allocator, solution_node).unwrap_or_default();
151
152 spends.push(CoinSpend {
153 coin: Coin::from_protocol(&removal),
154 puzzle_reveal: format!("0x{}", hex::encode(&puzzle_bytes)),
155 solution: format!("0x{}", hex::encode(&solution_bytes)),
156 });
157 }
158 }
159
160 Ok(spends)
161}
162
163pub fn block_spends_with_conditions(
170 block: &FullBlock,
171 constants: &ConsensusConstants,
172) -> Result<Vec<CoinSpendWithConditions>, ChiaQueryError> {
173 let generator = match &block.transactions_generator {
174 Some(g) => g,
175 None => return Ok(Vec::new()),
176 };
177
178 let flags = DONT_VALIDATE_SIGNATURE;
179 let block_refs: Vec<&[u8]> = Vec::new();
180 let mut allocator = make_allocator(flags);
181
182 let conds = run_block_generator2(
183 &mut allocator,
184 generator.as_ref(),
185 &block_refs,
186 constants.max_block_cost_clvm,
187 flags,
188 &Signature::default(),
189 None,
190 constants,
191 )
192 .map_err(|e| ChiaQueryError::PeerConnection(format!("run_block_generator2 failed: {e:?}")))?;
193
194 let program = node_from_bytes_backrefs(&mut allocator, generator.as_ref())
195 .map_err(|e| ChiaQueryError::PeerConnection(format!("parse generator: {e:?}")))?;
196
197 let args = chia_consensus::run_block_generator::setup_generator_args(
198 &mut allocator,
199 &block_refs,
200 flags,
201 )
202 .map_err(|e| ChiaQueryError::PeerConnection(format!("setup args: {e:?}")))?;
203
204 let dialect = clvmr::chia_dialect::ChiaDialect::new(flags);
205 let reduction = clvmr::run_program::run_program(
206 &mut allocator,
207 &dialect,
208 program,
209 args,
210 constants.max_block_cost_clvm,
211 )
212 .map_err(|e| ChiaQueryError::PeerConnection(format!("run_program: {e:?}")))?;
213
214 let generator_output = reduction.1;
215
216 let mut result = Vec::new();
217 for sc in &conds.spends {
218 let parent_id: Bytes32 = allocator.atom(sc.parent_id).as_ref().try_into().unwrap();
219 let puzzle_hash: Bytes32 = allocator.atom(sc.puzzle_hash).as_ref().try_into().unwrap();
220 let removal = chia_protocol::Coin {
221 parent_coin_info: parent_id,
222 puzzle_hash,
223 amount: sc.coin_amount,
224 };
225
226 if let Ok((puzzle_node, solution_node)) =
227 get_puzzle_and_solution_for_coin(&allocator, generator_output, &removal)
228 {
229 let puzzle_bytes = node_to_bytes(&allocator, puzzle_node).unwrap_or_default();
230 let solution_bytes = node_to_bytes(&allocator, solution_node).unwrap_or_default();
231
232 let conditions = match clvmr::run_program::run_program(
234 &mut allocator,
235 &dialect,
236 puzzle_node,
237 solution_node,
238 constants.max_block_cost_clvm,
239 ) {
240 Ok(clvmr::reduction::Reduction(_, output)) => parse_conditions(&allocator, output),
241 Err(_) => Vec::new(),
242 };
243
244 result.push(CoinSpendWithConditions {
245 coin_spend: CoinSpend {
246 coin: Coin::from_protocol(&removal),
247 puzzle_reveal: format!("0x{}", hex::encode(&puzzle_bytes)),
248 solution: format!("0x{}", hex::encode(&solution_bytes)),
249 },
250 conditions,
251 });
252 }
253 }
254
255 Ok(result)
256}
257
258fn parse_conditions(allocator: &clvmr::Allocator, output: clvmr::NodePtr) -> Vec<Condition> {
265 parse_conditions_public(allocator, output)
266}
267
268pub fn parse_conditions_public(
270 allocator: &clvmr::Allocator,
271 mut output: clvmr::NodePtr,
272) -> Vec<Condition> {
273 let mut conditions = Vec::new();
274
275 while let Ok(Some((cond, rest))) = validation_error::next(allocator, output) {
276 output = rest;
277
278 let Ok(opcode_node) = validation_error::first(allocator, cond) else {
280 continue;
281 };
282
283 let opcode_bytes = match allocator.sexp(opcode_node) {
284 clvmr::allocator::SExp::Atom => allocator.atom(opcode_node).as_ref().to_vec(),
285 clvmr::allocator::SExp::Pair(_, _) => continue,
286 };
287 let opcode = serde_json::Value::String(format!("0x{}", hex::encode(&opcode_bytes)));
288
289 let mut vars = Vec::new();
291 let Ok(mut args_iter) = validation_error::rest(allocator, cond) else {
292 continue;
293 };
294
295 while let Ok(Some((arg, rest))) = validation_error::next(allocator, args_iter) {
296 args_iter = rest;
297 let arg_hex = match allocator.sexp(arg) {
300 clvmr::allocator::SExp::Atom => {
301 format!("0x{}", hex::encode(allocator.atom(arg).as_ref()))
302 }
303 clvmr::allocator::SExp::Pair(_, _) => {
304 match clvmr::serde::node_to_bytes(allocator, arg) {
305 Ok(bytes) => format!("0x{}", hex::encode(&bytes)),
306 Err(_) => continue,
307 }
308 }
309 };
310 vars.push(arg_hex);
311 }
312
313 conditions.push(Condition { opcode, vars });
314 }
315
316 conditions
317}
318
319fn reward_coins(block: &FullBlock, height: u32) -> Vec<CoinRecord> {
324 let Some(ref ti) = block.transactions_info else {
325 return Vec::new();
326 };
327 let timestamp = block_timestamp(block);
328 ti.reward_claims_incorporated
329 .iter()
330 .map(|c| CoinRecord {
331 coin: Coin::from_protocol(c),
332 confirmed_block_index: height,
333 spent_block_index: 0,
334 spent: false,
335 coinbase: true,
336 timestamp,
337 })
338 .collect()
339}
340
341fn block_timestamp(block: &FullBlock) -> u64 {
342 block
343 .foliage_transaction_block
344 .as_ref()
345 .map(|ft| ft.timestamp)
346 .unwrap_or(0)
347}