1use chia_sha2::Sha256;
2use chia_streamable_macro::streamable;
3
4use crate::Bytes;
5use crate::Bytes32;
6use crate::EndOfSubSlotBundle;
7use crate::Program;
8use crate::RewardChainBlockUnfinished;
9use crate::VDFProof;
10use crate::{Foliage, FoliageTransactionBlock, TransactionsInfo};
11use chia_traits::Streamable;
12use chia_traits::chia_error::{Error, Result};
13use std::io::Cursor;
14
15#[streamable(no_streamable)]
21pub struct UnfinishedBlock {
22 finished_sub_slots: Vec<EndOfSubSlotBundle>, reward_chain_block: RewardChainBlockUnfinished, challenge_chain_sp_proof: Option<VDFProof>, reward_chain_sp_proof: Option<VDFProof>, foliage: Foliage, foliage_transaction_block: Option<FoliageTransactionBlock>, transactions_info: Option<TransactionsInfo>, transactions_generator: Option<Program>, transactions_generator_ref_list: Vec<u32>, transactions_generator_buffer: Option<Bytes>,
36
37 version: u8,
40}
41
42impl Streamable for UnfinishedBlock {
43 fn update_digest(&self, digest: &mut Sha256) {
44 self.finished_sub_slots.update_digest(digest);
45 self.reward_chain_block.update_digest(digest);
46 self.challenge_chain_sp_proof.update_digest(digest);
47 self.reward_chain_sp_proof.update_digest(digest);
48 self.foliage.update_digest(digest);
49 self.foliage_transaction_block.update_digest(digest);
50 self.transactions_info.update_digest(digest);
51
52 if self.version == 0 {
53 self.transactions_generator.update_digest(digest);
54 self.transactions_generator_ref_list.update_digest(digest);
55 } else if self.version == 1 {
56 match &self.transactions_generator_buffer {
57 None => {
58 0b10_u8.update_digest(digest);
59 }
60 Some(buf) => {
61 0b11_u8.update_digest(digest);
62 buf.update_digest(digest);
63 }
64 }
65 } else {
66 digest.update(b"invalid-unfinished-block-version");
67 }
68 }
69
70 fn stream(&self, out: &mut Vec<u8>) -> Result<()> {
71 self.finished_sub_slots.stream(out)?;
72 self.reward_chain_block.stream(out)?;
73 self.challenge_chain_sp_proof.stream(out)?;
74 self.reward_chain_sp_proof.stream(out)?;
75 self.foliage.stream(out)?;
76 self.foliage_transaction_block.stream(out)?;
77 self.transactions_info.stream(out)?;
78
79 if self.version == 0 {
80 self.transactions_generator.stream(out)?;
81 self.transactions_generator_ref_list.stream(out)?;
82 } else if self.version == 1 {
83 match &self.transactions_generator_buffer {
84 None => {
85 0b10_u8.stream(out)?;
86 }
87 Some(buf) => {
88 0b11_u8.stream(out)?;
89 buf.stream(out)?;
90 }
91 }
92 } else {
93 return Err(Error::InvalidUnfinishedBlock);
94 }
95 Ok(())
96 }
97
98 fn parse<const TRUSTED: bool>(input: &mut Cursor<&[u8]>) -> Result<Self> {
99 let finished_sub_slots = <Vec<EndOfSubSlotBundle> as Streamable>::parse::<TRUSTED>(input)?;
100 let reward_chain_block =
101 <RewardChainBlockUnfinished as Streamable>::parse::<TRUSTED>(input)?;
102 let challenge_chain_sp_proof = <Option<VDFProof> as Streamable>::parse::<TRUSTED>(input)?;
103 let reward_chain_sp_proof = <Option<VDFProof> as Streamable>::parse::<TRUSTED>(input)?;
104 let foliage = <Foliage as Streamable>::parse::<TRUSTED>(input)?;
105 let foliage_transaction_block =
106 <Option<FoliageTransactionBlock> as Streamable>::parse::<TRUSTED>(input)?;
107 let transactions_info = <Option<TransactionsInfo> as Streamable>::parse::<TRUSTED>(input)?;
108
109 let prefix = <u8 as Streamable>::parse::<TRUSTED>(input)?;
110 let version = prefix >> 1;
111 let has_generator = (prefix & 1) != 0;
112
113 if version == 0 {
114 let transactions_generator = if has_generator {
115 Some(<Program as Streamable>::parse::<TRUSTED>(input)?)
116 } else {
117 None
118 };
119 let transactions_generator_ref_list =
120 <Vec<u32> as Streamable>::parse::<TRUSTED>(input)?;
121
122 Ok(UnfinishedBlock {
123 finished_sub_slots,
124 reward_chain_block,
125 challenge_chain_sp_proof,
126 reward_chain_sp_proof,
127 foliage,
128 foliage_transaction_block,
129 transactions_info,
130 transactions_generator,
131 transactions_generator_ref_list,
132 transactions_generator_buffer: None,
133 version,
134 })
135 } else if version == 1 {
136 let transactions_generator_buffer = if has_generator {
137 Some(<Bytes as Streamable>::parse::<TRUSTED>(input)?)
138 } else {
139 None
140 };
141
142 Ok(UnfinishedBlock {
143 finished_sub_slots,
144 reward_chain_block,
145 challenge_chain_sp_proof,
146 reward_chain_sp_proof,
147 foliage,
148 foliage_transaction_block,
149 transactions_info,
150 transactions_generator: None,
151 transactions_generator_ref_list: vec![],
152 transactions_generator_buffer,
153 version,
154 })
155 } else {
156 Err(Error::InvalidUnfinishedBlock)
157 }
158 }
159}
160
161impl UnfinishedBlock {
162 pub fn prev_header_hash(&self) -> Bytes32 {
163 self.foliage.prev_block_hash
164 }
165
166 pub fn partial_hash(&self) -> Bytes32 {
167 self.reward_chain_block.hash().into()
168 }
169
170 pub fn is_transaction_block(&self) -> bool {
171 self.foliage.foliage_transaction_block_hash.is_some()
172 }
173
174 pub fn total_iters(&self) -> u128 {
175 self.reward_chain_block.total_iters
176 }
177}
178
179#[cfg(feature = "py-bindings")]
180use chia_traits::ChiaToPython;
181#[cfg(feature = "py-bindings")]
182use pyo3::prelude::*;
183
184#[cfg(feature = "py-bindings")]
185#[pymethods]
186impl UnfinishedBlock {
187 #[getter]
188 #[pyo3(name = "prev_header_hash")]
189 fn py_prev_header_hash(&self) -> Bytes32 {
190 self.prev_header_hash()
191 }
192
193 #[getter]
194 #[pyo3(name = "partial_hash")]
195 fn py_partial_hash(&self) -> Bytes32 {
196 self.partial_hash()
197 }
198
199 #[pyo3(name = "is_transaction_block")]
200 fn py_is_transaction_block(&self) -> bool {
201 self.is_transaction_block()
202 }
203
204 #[getter]
205 #[pyo3(name = "total_iters")]
206 fn py_total_iters<'a>(&self, py: Python<'a>) -> PyResult<Bound<'a, PyAny>> {
207 ChiaToPython::to_python(&self.total_iters(), py)
208 }
209}
210
211#[cfg(test)]
212mod tests {
213 use super::*;
214 use crate::{FoliageBlockData, PoolTarget, ProofOfSpace};
215 use chia_bls::{G1Element, G2Element};
216 use rstest::rstest;
217
218 fn make_proof_of_space() -> ProofOfSpace {
219 ProofOfSpace::new(
220 Bytes32::default(),
221 Some(G1Element::default()),
222 None,
223 G1Element::default(),
224 0,
225 0,
226 0,
227 0,
228 32,
229 Bytes::from(vec![0x80]),
230 )
231 }
232
233 fn make_reward_chain_block_unfinished() -> RewardChainBlockUnfinished {
234 RewardChainBlockUnfinished::new(
235 1,
236 0,
237 Bytes32::default(),
238 make_proof_of_space(),
239 None,
240 G2Element::default(),
241 None,
242 G2Element::default(),
243 )
244 }
245
246 fn make_foliage() -> Foliage {
247 let pool_target = PoolTarget::new(Bytes32::default(), 0);
248 let foliage_block_data = FoliageBlockData::new(
249 Bytes32::default(),
250 pool_target,
251 Some(G2Element::default()),
252 Bytes32::default(),
253 Bytes32::default(),
254 );
255 Foliage::new(
256 Bytes32::default(),
257 Bytes32::default(),
258 foliage_block_data,
259 G2Element::default(),
260 None,
261 None,
262 )
263 }
264
265 fn make_v0_block(generator: Option<Program>, ref_list: Vec<u32>) -> UnfinishedBlock {
266 UnfinishedBlock::new(
267 vec![],
268 make_reward_chain_block_unfinished(),
269 None,
270 None,
271 make_foliage(),
272 None,
273 None,
274 generator,
275 ref_list,
276 None,
277 0,
278 )
279 }
280
281 fn make_v1_block(buffer: Option<Vec<u8>>) -> UnfinishedBlock {
282 UnfinishedBlock::new(
283 vec![],
284 make_reward_chain_block_unfinished(),
285 None,
286 None,
287 make_foliage(),
288 None,
289 None,
290 None,
291 vec![],
292 buffer.map(Bytes::from),
293 1,
294 )
295 }
296
297 #[test]
298 fn v0_no_generator_roundtrip() {
299 let block = make_v0_block(None, vec![]);
300 let buf = block.to_bytes().unwrap();
301 let block2 = UnfinishedBlock::parse::<false>(&mut Cursor::new(&buf)).unwrap();
302
303 assert_eq!(block2.version, 0);
304 assert!(block2.transactions_generator.is_none());
305 assert!(block2.transactions_generator_ref_list.is_empty());
306 assert!(block2.transactions_generator_buffer.is_none());
307 assert_eq!(block2.to_bytes().unwrap(), buf);
308 }
309
310 #[test]
311 fn v0_with_generator_roundtrip() {
312 let generator = Program::from(vec![0xff, 0x01, 0x80]);
313 let block = make_v0_block(Some(generator.clone()), vec![100, 200]);
314 let buf = block.to_bytes().unwrap();
315 let block2 = UnfinishedBlock::parse::<false>(&mut Cursor::new(&buf)).unwrap();
316
317 assert_eq!(block2.version, 0);
318 assert_eq!(
319 block2.transactions_generator.as_ref().unwrap().as_ref(),
320 generator.as_ref()
321 );
322 assert_eq!(block2.transactions_generator_ref_list, vec![100, 200]);
323 assert!(block2.transactions_generator_buffer.is_none());
324 assert_eq!(block2.to_bytes().unwrap(), buf);
325 }
326
327 #[test]
328 fn v1_no_generator_roundtrip() {
329 let block = make_v1_block(None);
330 let buf = block.to_bytes().unwrap();
331 let block2 = UnfinishedBlock::parse::<false>(&mut Cursor::new(&buf)).unwrap();
332
333 assert_eq!(block2.version, 1);
334 assert!(block2.transactions_generator.is_none());
335 assert!(block2.transactions_generator_ref_list.is_empty());
336 assert!(block2.transactions_generator_buffer.is_none());
337 assert_eq!(block2.to_bytes().unwrap(), buf);
338 }
339
340 #[test]
341 fn v1_with_buffer_roundtrip() {
342 let raw = vec![0xde, 0xad, 0xbe, 0xef, 0xca, 0xfe];
343 let block = make_v1_block(Some(raw.clone()));
344 let buf = block.to_bytes().unwrap();
345 let block2 = UnfinishedBlock::parse::<false>(&mut Cursor::new(&buf)).unwrap();
346
347 assert_eq!(block2.version, 1);
348 assert!(block2.transactions_generator.is_none());
349 assert!(block2.transactions_generator_ref_list.is_empty());
350 assert_eq!(
351 block2
352 .transactions_generator_buffer
353 .as_ref()
354 .unwrap()
355 .to_vec(),
356 raw
357 );
358 assert_eq!(block2.to_bytes().unwrap(), buf);
359 }
360
361 #[rstest]
362 #[case::v0(0, 0b00, 0b01)]
363 #[case::v1(1, 0b10, 0b11)]
364 fn prefix_byte_encoding(
365 #[case] version: u8,
366 #[case] expected_none: u8,
367 #[case] expected_some: u8,
368 ) {
369 let (buf_none, buf_some) = if version == 0 {
370 (
371 make_v0_block(None, vec![]).to_bytes().unwrap(),
372 make_v0_block(Some(Program::from(vec![0x80])), vec![])
373 .to_bytes()
374 .unwrap(),
375 )
376 } else {
377 (
378 make_v1_block(None).to_bytes().unwrap(),
379 make_v1_block(Some(vec![0x80])).to_bytes().unwrap(),
380 )
381 };
382
383 let prefix_offset = buf_none
384 .iter()
385 .zip(buf_some.iter())
386 .position(|(a, b)| a != b)
387 .unwrap();
388
389 assert_eq!(buf_none[prefix_offset], expected_none);
390 assert_eq!(buf_some[prefix_offset], expected_some);
391 }
392
393 #[test]
394 fn v1_generator_has_length_prefix() {
395 let raw = vec![0xca, 0xfe, 0xba, 0xbe];
396 let block = make_v1_block(Some(raw.clone()));
397 let buf = block.to_bytes().unwrap();
398
399 let block_empty = make_v1_block(None);
400 let buf_empty = block_empty.to_bytes().unwrap();
401
402 let prefix_offset = buf
403 .iter()
404 .zip(buf_empty.iter())
405 .position(|(a, b)| a != b)
406 .unwrap();
407
408 assert_eq!(buf[prefix_offset], 0b11);
409 let len = u32::from_be_bytes(
410 buf[prefix_offset + 1..prefix_offset + 5]
411 .try_into()
412 .unwrap(),
413 );
414 assert_eq!(len as usize, raw.len());
415 assert_eq!(&buf[prefix_offset + 5..prefix_offset + 5 + raw.len()], &raw);
416 assert_eq!(prefix_offset + 5 + raw.len(), buf.len());
417 }
418
419 #[test]
420 fn v1_omits_ref_list() {
421 let block_v0 = make_v0_block(Some(Program::from(vec![0x80])), vec![42]);
422 let buf_v0 = block_v0.to_bytes().unwrap();
423
424 let block_v1 = make_v1_block(Some(vec![0x80]));
425 let buf_v1 = block_v1.to_bytes().unwrap();
426
427 assert!(buf_v1.len() < buf_v0.len());
428 }
429
430 #[test]
431 fn v1_unvalidated_buffer_roundtrips() {
432 let garbage = vec![0xff; 1000];
433 let block = make_v1_block(Some(garbage.clone()));
434 let buf = block.to_bytes().unwrap();
435 let block2 = UnfinishedBlock::parse::<false>(&mut Cursor::new(&buf)).unwrap();
436 assert_eq!(
437 block2.transactions_generator_buffer.unwrap().to_vec(),
438 garbage
439 );
440 }
441
442 #[test]
448 fn high_prefix_bits_rejected() {
449 let v0_none = make_v0_block(None, vec![]).to_bytes().unwrap();
450 let v0_some = make_v0_block(Some(Program::from(vec![0x80])), vec![])
451 .to_bytes()
452 .unwrap();
453 let offset = v0_none
454 .iter()
455 .zip(v0_some.iter())
456 .position(|(a, b)| a != b)
457 .unwrap();
458 assert_eq!(v0_none[offset], 0b00);
459
460 let v1_none = make_v1_block(None).to_bytes().unwrap();
461 assert_eq!(v1_none[offset], 0b10);
462
463 for valid in [&v0_none, &v1_none] {
464 for bit in 2..8u8 {
465 let mut buf = valid.clone();
466 buf[offset] |= 1 << bit;
467 let err = UnfinishedBlock::parse::<false>(&mut Cursor::new(&buf))
468 .expect_err("high prefix bit must be rejected");
469 assert_eq!(err, Error::InvalidUnfinishedBlock);
470 }
471 }
472 }
473}