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_record, 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
63 .iter()
64 .map(|coin| CoinRecord {
65 coin: Coin::from_protocol(coin),
66 confirmed_block_index: 0,
67 spent_block_index: height,
68 spent: true,
69 coinbase: false,
70 timestamp: 0,
71 })
72 .collect();
73
74 Ok(AdditionsAndRemovals {
75 additions,
76 removals,
77 })
78}
79
80pub fn block_spends(
87 block: &FullBlock,
88 constants: &ConsensusConstants,
89) -> Result<Vec<CoinSpend>, ChiaQueryError> {
90 let generator = match &block.transactions_generator {
91 Some(g) => g,
92 None => return Ok(Vec::new()),
93 };
94
95 let flags = DONT_VALIDATE_SIGNATURE;
96 let block_refs: Vec<&[u8]> = Vec::new();
97 let mut allocator = make_allocator(flags);
98
99 let conds = run_block_generator2(
100 &mut allocator,
101 generator.as_ref(),
102 &block_refs,
103 constants.max_block_cost_clvm,
104 flags,
105 &Signature::default(),
106 None,
107 constants,
108 )
109 .map_err(|e| ChiaQueryError::PeerConnection(format!("run_block_generator2 failed: {e:?}")))?;
110
111 let (program, backrefs) =
112 node_from_bytes_backrefs_record(&mut allocator, generator.as_ref())
113 .map_err(|e| ChiaQueryError::PeerConnection(format!("parse generator: {e:?}")))?;
114
115 let args =
116 chia::consensus::run_block_generator::setup_generator_args(&mut allocator, &block_refs)
117 .map_err(|e| ChiaQueryError::PeerConnection(format!("setup args: {e:?}")))?;
118
119 let dialect = clvmr::chia_dialect::ChiaDialect::new(flags);
120 let reduction = clvmr::run_program::run_program(
121 &mut allocator,
122 &dialect,
123 program,
124 args,
125 constants.max_block_cost_clvm,
126 )
127 .map_err(|e| ChiaQueryError::PeerConnection(format!("run_program: {e:?}")))?;
128
129 let generator_output = reduction.1;
130
131 let mut spends = Vec::new();
132 for sc in &conds.spends {
133 let parent_id: Bytes32 = allocator.atom(sc.parent_id).as_ref().try_into().unwrap();
134 let puzzle_hash: Bytes32 = allocator.atom(sc.puzzle_hash).as_ref().try_into().unwrap();
135 let removal = chia::protocol::Coin {
136 parent_coin_info: parent_id,
137 puzzle_hash,
138 amount: sc.coin_amount,
139 };
140
141 if let Ok((puzzle_node, solution_node)) =
142 get_puzzle_and_solution_for_coin(&allocator, generator_output, &backrefs, &removal)
143 {
144 let puzzle_bytes = node_to_bytes(&allocator, puzzle_node).unwrap_or_default();
145 let solution_bytes = node_to_bytes(&allocator, solution_node).unwrap_or_default();
146
147 spends.push(CoinSpend {
148 coin: Coin::from_protocol(&removal),
149 puzzle_reveal: format!("0x{}", hex::encode(&puzzle_bytes)),
150 solution: format!("0x{}", hex::encode(&solution_bytes)),
151 });
152 }
153 }
154
155 Ok(spends)
156}
157
158pub fn block_spends_with_conditions(
165 block: &FullBlock,
166 constants: &ConsensusConstants,
167) -> Result<Vec<CoinSpendWithConditions>, ChiaQueryError> {
168 let generator = match &block.transactions_generator {
169 Some(g) => g,
170 None => return Ok(Vec::new()),
171 };
172
173 let flags = DONT_VALIDATE_SIGNATURE;
174 let block_refs: Vec<&[u8]> = Vec::new();
175 let mut allocator = make_allocator(flags);
176
177 let conds = run_block_generator2(
178 &mut allocator,
179 generator.as_ref(),
180 &block_refs,
181 constants.max_block_cost_clvm,
182 flags,
183 &Signature::default(),
184 None,
185 constants,
186 )
187 .map_err(|e| ChiaQueryError::PeerConnection(format!("run_block_generator2 failed: {e:?}")))?;
188
189 let (program, backrefs) =
190 node_from_bytes_backrefs_record(&mut allocator, generator.as_ref())
191 .map_err(|e| ChiaQueryError::PeerConnection(format!("parse generator: {e:?}")))?;
192
193 let args =
194 chia::consensus::run_block_generator::setup_generator_args(&mut allocator, &block_refs)
195 .map_err(|e| ChiaQueryError::PeerConnection(format!("setup args: {e:?}")))?;
196
197 let dialect = clvmr::chia_dialect::ChiaDialect::new(flags);
198 let reduction = clvmr::run_program::run_program(
199 &mut allocator,
200 &dialect,
201 program,
202 args,
203 constants.max_block_cost_clvm,
204 )
205 .map_err(|e| ChiaQueryError::PeerConnection(format!("run_program: {e:?}")))?;
206
207 let generator_output = reduction.1;
208
209 let mut result = Vec::new();
210 for sc in &conds.spends {
211 let parent_id: Bytes32 = allocator.atom(sc.parent_id).as_ref().try_into().unwrap();
212 let puzzle_hash: Bytes32 = allocator.atom(sc.puzzle_hash).as_ref().try_into().unwrap();
213 let removal = chia::protocol::Coin {
214 parent_coin_info: parent_id,
215 puzzle_hash,
216 amount: sc.coin_amount,
217 };
218
219 if let Ok((puzzle_node, solution_node)) =
220 get_puzzle_and_solution_for_coin(&allocator, generator_output, &backrefs, &removal)
221 {
222 let puzzle_bytes = node_to_bytes(&allocator, puzzle_node).unwrap_or_default();
223 let solution_bytes = node_to_bytes(&allocator, solution_node).unwrap_or_default();
224
225 let conditions = match clvmr::run_program::run_program(
227 &mut allocator,
228 &dialect,
229 puzzle_node,
230 solution_node,
231 constants.max_block_cost_clvm,
232 ) {
233 Ok(clvmr::reduction::Reduction(_, output)) => parse_conditions(&allocator, output),
234 Err(_) => Vec::new(),
235 };
236
237 result.push(CoinSpendWithConditions {
238 coin_spend: CoinSpend {
239 coin: Coin::from_protocol(&removal),
240 puzzle_reveal: format!("0x{}", hex::encode(&puzzle_bytes)),
241 solution: format!("0x{}", hex::encode(&solution_bytes)),
242 },
243 conditions,
244 });
245 }
246 }
247
248 Ok(result)
249}
250
251fn parse_conditions(allocator: &clvmr::Allocator, output: clvmr::NodePtr) -> Vec<Condition> {
258 parse_conditions_public(allocator, output)
259}
260
261pub fn parse_conditions_public(
263 allocator: &clvmr::Allocator,
264 mut output: clvmr::NodePtr,
265) -> Vec<Condition> {
266 let mut conditions = Vec::new();
267
268 while let Ok(Some((cond, rest))) = validation_error::next(allocator, output) {
269 output = rest;
270
271 let Ok(opcode_node) = validation_error::first(allocator, cond) else {
273 continue;
274 };
275
276 let opcode_bytes = match allocator.sexp(opcode_node) {
277 clvmr::allocator::SExp::Atom => allocator.atom(opcode_node).as_ref().to_vec(),
278 clvmr::allocator::SExp::Pair(_, _) => continue,
279 };
280 let opcode = serde_json::Value::String(format!("0x{}", hex::encode(&opcode_bytes)));
281
282 let mut vars = Vec::new();
284 let Ok(mut args_iter) = validation_error::rest(allocator, cond) else {
285 continue;
286 };
287
288 while let Ok(Some((arg, rest))) = validation_error::next(allocator, args_iter) {
289 args_iter = rest;
290 let arg_hex = match allocator.sexp(arg) {
293 clvmr::allocator::SExp::Atom => {
294 format!("0x{}", hex::encode(allocator.atom(arg).as_ref()))
295 }
296 clvmr::allocator::SExp::Pair(_, _) => {
297 match clvmr::serde::node_to_bytes(allocator, arg) {
298 Ok(bytes) => format!("0x{}", hex::encode(&bytes)),
299 Err(_) => continue,
300 }
301 }
302 };
303 vars.push(arg_hex);
304 }
305
306 conditions.push(Condition { opcode, vars });
307 }
308
309 conditions
310}
311
312fn reward_coins(block: &FullBlock, height: u32) -> Vec<CoinRecord> {
317 let Some(ref ti) = block.transactions_info else {
318 return Vec::new();
319 };
320 let timestamp = block_timestamp(block);
321 ti.reward_claims_incorporated
322 .iter()
323 .map(|c| CoinRecord {
324 coin: Coin::from_protocol(c),
325 confirmed_block_index: height,
326 spent_block_index: 0,
327 spent: false,
328 coinbase: true,
329 timestamp,
330 })
331 .collect()
332}
333
334fn block_timestamp(block: &FullBlock) -> u64 {
335 block
336 .foliage_transaction_block
337 .as_ref()
338 .map(|ft| ft.timestamp)
339 .unwrap_or(0)
340}