1use chia_sha2::Sha256;
2use chia_streamable_macro::streamable;
3
4use crate::Bytes;
5use crate::Bytes32;
6use crate::Coin;
7use crate::EndOfSubSlotBundle;
8use crate::Program;
9use crate::RewardChainBlock;
10use crate::VDFProof;
11use crate::{Foliage, FoliageTransactionBlock, TransactionsInfo};
12use chia_traits::Streamable;
13use chia_traits::chia_error::{Error, Result};
14use std::io::Cursor;
15
16#[streamable(no_streamable)]
22pub struct FullBlock {
23 finished_sub_slots: Vec<EndOfSubSlotBundle>,
24 reward_chain_block: RewardChainBlock,
25 challenge_chain_sp_proof: Option<VDFProof>, challenge_chain_ip_proof: VDFProof,
27 reward_chain_sp_proof: Option<VDFProof>, reward_chain_ip_proof: VDFProof,
29 infused_challenge_chain_ip_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>,
39
40 version: u8,
43}
44
45impl Streamable for FullBlock {
46 fn update_digest(&self, digest: &mut Sha256) {
47 self.finished_sub_slots.update_digest(digest);
48 self.reward_chain_block.update_digest(digest);
49 self.challenge_chain_sp_proof.update_digest(digest);
50 self.challenge_chain_ip_proof.update_digest(digest);
51 self.reward_chain_sp_proof.update_digest(digest);
52 self.reward_chain_ip_proof.update_digest(digest);
53 self.infused_challenge_chain_ip_proof.update_digest(digest);
54 self.foliage.update_digest(digest);
55 self.foliage_transaction_block.update_digest(digest);
56 self.transactions_info.update_digest(digest);
57
58 if self.version == 0 {
59 self.transactions_generator.update_digest(digest);
60 self.transactions_generator_ref_list.update_digest(digest);
61 } else if self.version == 1 {
62 match &self.transactions_generator_buffer {
63 None => {
64 0b10_u8.update_digest(digest);
65 }
66 Some(buf) => {
67 0b11_u8.update_digest(digest);
68 buf.update_digest(digest);
69 }
70 }
71 } else {
72 panic!("version field must be 0 or 1, but it's {}", self.version);
73 }
74 }
75
76 fn stream(&self, out: &mut Vec<u8>) -> Result<()> {
77 self.finished_sub_slots.stream(out)?;
78 self.reward_chain_block.stream(out)?;
79 self.challenge_chain_sp_proof.stream(out)?;
80 self.challenge_chain_ip_proof.stream(out)?;
81 self.reward_chain_sp_proof.stream(out)?;
82 self.reward_chain_ip_proof.stream(out)?;
83 self.infused_challenge_chain_ip_proof.stream(out)?;
84 self.foliage.stream(out)?;
85 self.foliage_transaction_block.stream(out)?;
86 self.transactions_info.stream(out)?;
87
88 if self.version == 0 {
89 self.transactions_generator.stream(out)?;
90 self.transactions_generator_ref_list.stream(out)?;
91 } else if self.version == 1 {
92 match &self.transactions_generator_buffer {
93 None => {
94 0b10_u8.stream(out)?;
95 }
96 Some(buf) => {
97 0b11_u8.stream(out)?;
98 buf.stream(out)?;
99 }
100 }
101 } else {
102 return Err(Error::InvalidFullBlock);
103 }
104 Ok(())
105 }
106
107 fn parse<const TRUSTED: bool>(input: &mut Cursor<&[u8]>) -> Result<Self> {
108 let finished_sub_slots = <Vec<EndOfSubSlotBundle> as Streamable>::parse::<TRUSTED>(input)?;
109 let reward_chain_block = <RewardChainBlock as Streamable>::parse::<TRUSTED>(input)?;
110 let challenge_chain_sp_proof = <Option<VDFProof> as Streamable>::parse::<TRUSTED>(input)?;
111 let challenge_chain_ip_proof = <VDFProof as Streamable>::parse::<TRUSTED>(input)?;
112 let reward_chain_sp_proof = <Option<VDFProof> as Streamable>::parse::<TRUSTED>(input)?;
113 let reward_chain_ip_proof = <VDFProof as Streamable>::parse::<TRUSTED>(input)?;
114 let infused_challenge_chain_ip_proof =
115 <Option<VDFProof> as Streamable>::parse::<TRUSTED>(input)?;
116 let foliage = <Foliage as Streamable>::parse::<TRUSTED>(input)?;
117 let foliage_transaction_block =
118 <Option<FoliageTransactionBlock> as Streamable>::parse::<TRUSTED>(input)?;
119 let transactions_info = <Option<TransactionsInfo> as Streamable>::parse::<TRUSTED>(input)?;
120
121 let prefix = <u8 as Streamable>::parse::<TRUSTED>(input)?;
122 let version = prefix >> 1;
123 let has_generator = (prefix & 1) != 0;
124
125 if version == 0 {
126 let transactions_generator = if has_generator {
127 Some(<Program as Streamable>::parse::<TRUSTED>(input)?)
128 } else {
129 None
130 };
131 let transactions_generator_ref_list =
132 <Vec<u32> as Streamable>::parse::<TRUSTED>(input)?;
133
134 Ok(FullBlock {
135 finished_sub_slots,
136 reward_chain_block,
137 challenge_chain_sp_proof,
138 challenge_chain_ip_proof,
139 reward_chain_sp_proof,
140 reward_chain_ip_proof,
141 infused_challenge_chain_ip_proof,
142 foliage,
143 foliage_transaction_block,
144 transactions_info,
145 transactions_generator,
146 transactions_generator_ref_list,
147 transactions_generator_buffer: None,
148 version,
149 })
150 } else if version == 1 {
151 let transactions_generator_buffer = if has_generator {
152 Some(<Bytes as Streamable>::parse::<TRUSTED>(input)?)
153 } else {
154 None
155 };
156
157 Ok(FullBlock {
158 finished_sub_slots,
159 reward_chain_block,
160 challenge_chain_sp_proof,
161 challenge_chain_ip_proof,
162 reward_chain_sp_proof,
163 reward_chain_ip_proof,
164 infused_challenge_chain_ip_proof,
165 foliage,
166 foliage_transaction_block,
167 transactions_info,
168 transactions_generator: None,
169 transactions_generator_ref_list: vec![],
170 transactions_generator_buffer,
171 version,
172 })
173 } else {
174 Err(Error::InvalidFullBlock)
175 }
176 }
177}
178
179impl FullBlock {
180 pub fn prev_header_hash(&self) -> Bytes32 {
181 self.foliage.prev_block_hash
182 }
183
184 pub fn header_hash(&self) -> Bytes32 {
185 self.foliage.hash().into()
186 }
187
188 pub fn is_transaction_block(&self) -> bool {
189 self.foliage.foliage_transaction_block_hash.is_some()
190 }
191
192 pub fn total_iters(&self) -> u128 {
193 self.reward_chain_block.total_iters
194 }
195
196 pub fn height(&self) -> u32 {
197 self.reward_chain_block.height
198 }
199
200 pub fn weight(&self) -> u128 {
201 self.reward_chain_block.weight
202 }
203
204 pub fn get_included_reward_coins(&self) -> Vec<Coin> {
205 if let Some(ti) = &self.transactions_info {
206 ti.reward_claims_incorporated.clone()
207 } else {
208 vec![]
209 }
210 }
211
212 pub fn is_fully_compactified(&self) -> bool {
213 for sub_slot in &self.finished_sub_slots {
214 if sub_slot.proofs.challenge_chain_slot_proof.witness_type != 0
215 || !sub_slot
216 .proofs
217 .challenge_chain_slot_proof
218 .normalized_to_identity
219 {
220 return false;
221 }
222 if let Some(proof) = &sub_slot.proofs.infused_challenge_chain_slot_proof {
223 if proof.witness_type != 0 || !proof.normalized_to_identity {
224 return false;
225 }
226 }
227 }
228
229 if let Some(proof) = &self.challenge_chain_sp_proof {
230 if proof.witness_type != 0 || !proof.normalized_to_identity {
231 return false;
232 }
233 }
234 self.challenge_chain_ip_proof.witness_type == 0
235 && self.challenge_chain_ip_proof.normalized_to_identity
236 }
237}
238
239#[cfg(feature = "py-bindings")]
240use chia_traits::ChiaToPython;
241#[cfg(feature = "py-bindings")]
242use pyo3::prelude::*;
243
244#[cfg(feature = "py-bindings")]
245#[pymethods]
246impl FullBlock {
247 #[getter]
248 #[pyo3(name = "prev_header_hash")]
249 fn py_prev_header_hash(&self) -> Bytes32 {
250 self.prev_header_hash()
251 }
252
253 #[getter]
254 #[pyo3(name = "header_hash")]
255 fn py_header_hash(&self) -> Bytes32 {
256 self.header_hash()
257 }
258
259 #[pyo3(name = "is_transaction_block")]
260 fn py_is_transaction_block(&self) -> bool {
261 self.is_transaction_block()
262 }
263
264 #[getter]
265 #[pyo3(name = "total_iters")]
266 fn py_total_iters<'a>(&self, py: Python<'a>) -> PyResult<Bound<'a, PyAny>> {
267 ChiaToPython::to_python(&self.total_iters(), py)
268 }
269
270 #[getter]
271 #[pyo3(name = "height")]
272 fn py_height<'a>(&self, py: Python<'a>) -> PyResult<Bound<'a, PyAny>> {
273 ChiaToPython::to_python(&self.height(), py)
274 }
275
276 #[getter]
277 #[pyo3(name = "weight")]
278 fn py_weight<'a>(&self, py: Python<'a>) -> PyResult<Bound<'a, PyAny>> {
279 ChiaToPython::to_python(&self.weight(), py)
280 }
281
282 #[pyo3(name = "get_included_reward_coins")]
283 fn py_get_included_reward_coins(&self) -> Vec<Coin> {
284 self.get_included_reward_coins()
285 }
286
287 #[pyo3(name = "is_fully_compactified")]
288 fn py_is_fully_compactified(&self) -> bool {
289 self.is_fully_compactified()
290 }
291}
292
293#[cfg(test)]
294mod tests {
295 use super::*;
296 use crate::{ClassgroupElement, FoliageBlockData, PoolTarget, ProofOfSpace, VDFInfo};
297 use chia_bls::{G1Element, G2Element};
298
299 fn make_vdf_proof() -> VDFProof {
300 VDFProof::new(0, Bytes::default(), false)
301 }
302
303 fn make_vdf_info() -> VDFInfo {
304 VDFInfo::new(Bytes32::default(), 1, ClassgroupElement::default())
305 }
306
307 fn make_proof_of_space() -> ProofOfSpace {
308 ProofOfSpace::new(
309 Bytes32::default(),
310 Some(G1Element::default()),
311 None,
312 G1Element::default(),
313 0,
314 0,
315 0,
316 0,
317 32,
318 Bytes::from(vec![0x80]),
319 )
320 }
321
322 fn make_reward_chain_block() -> RewardChainBlock {
323 RewardChainBlock::new(
324 1,
325 0,
326 1,
327 0,
328 Bytes32::default(),
329 make_proof_of_space(),
330 None,
331 G2Element::default(),
332 make_vdf_info(),
333 None,
334 G2Element::default(),
335 make_vdf_info(),
336 None,
337 None,
338 false,
339 )
340 }
341
342 fn make_foliage() -> Foliage {
343 let pool_target = PoolTarget::new(Bytes32::default(), 0);
344 let foliage_block_data = FoliageBlockData::new(
345 Bytes32::default(),
346 pool_target,
347 Some(G2Element::default()),
348 Bytes32::default(),
349 Bytes32::default(),
350 );
351 Foliage::new(
352 Bytes32::default(),
353 Bytes32::default(),
354 foliage_block_data,
355 G2Element::default(),
356 None,
357 None,
358 )
359 }
360
361 fn make_v0_block(generator: Option<Program>, ref_list: Vec<u32>) -> FullBlock {
362 FullBlock::new(
363 vec![],
364 make_reward_chain_block(),
365 None,
366 make_vdf_proof(),
367 None,
368 make_vdf_proof(),
369 None,
370 make_foliage(),
371 None,
372 None,
373 generator,
374 ref_list,
375 None,
376 0,
377 )
378 }
379
380 fn make_v1_block(buffer: Option<Vec<u8>>) -> FullBlock {
381 FullBlock::new(
382 vec![],
383 make_reward_chain_block(),
384 None,
385 make_vdf_proof(),
386 None,
387 make_vdf_proof(),
388 None,
389 make_foliage(),
390 None,
391 None,
392 None,
393 vec![],
394 buffer.map(Bytes::from),
395 1,
396 )
397 }
398
399 #[test]
400 fn v0_no_generator_roundtrip() {
401 let block = make_v0_block(None, vec![]);
402 let buf = block.to_bytes().unwrap();
403 let block2 = FullBlock::parse::<false>(&mut Cursor::new(&buf)).unwrap();
404
405 assert_eq!(block2.version, 0);
406 assert!(block2.transactions_generator.is_none());
407 assert!(block2.transactions_generator_ref_list.is_empty());
408 assert!(block2.transactions_generator_buffer.is_none());
409 assert_eq!(block2.to_bytes().unwrap(), buf);
410 }
411
412 #[test]
413 fn v0_with_generator_roundtrip() {
414 let generator = Program::from(vec![0xff, 0x01, 0x80]);
415 let block = make_v0_block(Some(generator.clone()), vec![100, 200]);
416 let buf = block.to_bytes().unwrap();
417 let block2 = FullBlock::parse::<false>(&mut Cursor::new(&buf)).unwrap();
418
419 assert_eq!(block2.version, 0);
420 assert_eq!(
421 block2.transactions_generator.as_ref().unwrap().as_ref(),
422 generator.as_ref()
423 );
424 assert_eq!(block2.transactions_generator_ref_list, vec![100, 200]);
425 assert!(block2.transactions_generator_buffer.is_none());
426 assert_eq!(block2.to_bytes().unwrap(), buf);
427 }
428
429 #[test]
430 fn v1_no_generator_roundtrip() {
431 let block = make_v1_block(None);
432 let buf = block.to_bytes().unwrap();
433 let block2 = FullBlock::parse::<false>(&mut Cursor::new(&buf)).unwrap();
434
435 assert_eq!(block2.version, 1);
436 assert!(block2.transactions_generator.is_none());
437 assert!(block2.transactions_generator_ref_list.is_empty());
438 assert!(block2.transactions_generator_buffer.is_none());
439 assert_eq!(block2.to_bytes().unwrap(), buf);
440 }
441
442 #[test]
443 fn v1_with_buffer_roundtrip() {
444 let raw = vec![0xde, 0xad, 0xbe, 0xef, 0xca, 0xfe];
445 let block = make_v1_block(Some(raw.clone()));
446 let buf = block.to_bytes().unwrap();
447 let block2 = FullBlock::parse::<false>(&mut Cursor::new(&buf)).unwrap();
448
449 assert_eq!(block2.version, 1);
450 assert!(block2.transactions_generator.is_none());
451 assert!(block2.transactions_generator_ref_list.is_empty());
452 assert_eq!(
453 block2
454 .transactions_generator_buffer
455 .as_ref()
456 .unwrap()
457 .to_vec(),
458 raw
459 );
460 assert_eq!(block2.to_bytes().unwrap(), buf);
461 }
462
463 #[test]
464 fn v0_prefix_byte_encoding() {
465 let block_none = make_v0_block(None, vec![]);
466 let buf_none = block_none.to_bytes().unwrap();
467
468 let block_some = make_v0_block(Some(Program::from(vec![0x80])), vec![]);
469 let buf_some = block_some.to_bytes().unwrap();
470
471 let prefix_offset = buf_none
472 .iter()
473 .zip(buf_some.iter())
474 .position(|(a, b)| a != b)
475 .unwrap();
476
477 assert_eq!(buf_none[prefix_offset], 0b00);
478 assert_eq!(buf_some[prefix_offset], 0b01);
479 }
480
481 #[test]
482 fn v1_prefix_byte_encoding() {
483 let block_none = make_v1_block(None);
484 let buf_none = block_none.to_bytes().unwrap();
485
486 let block_some = make_v1_block(Some(vec![0x80]));
487 let buf_some = block_some.to_bytes().unwrap();
488
489 let prefix_offset = buf_none
490 .iter()
491 .zip(buf_some.iter())
492 .position(|(a, b)| a != b)
493 .unwrap();
494
495 assert_eq!(buf_none[prefix_offset], 0b10);
496 assert_eq!(buf_some[prefix_offset], 0b11);
497 }
498
499 #[test]
500 fn v1_generator_has_length_prefix() {
501 let raw = vec![0xca, 0xfe, 0xba, 0xbe];
502 let block = make_v1_block(Some(raw.clone()));
503 let buf = block.to_bytes().unwrap();
504
505 let block_empty = make_v1_block(None);
506 let buf_empty = block_empty.to_bytes().unwrap();
507
508 let prefix_offset = buf
509 .iter()
510 .zip(buf_empty.iter())
511 .position(|(a, b)| a != b)
512 .unwrap();
513
514 assert_eq!(buf[prefix_offset], 0b11);
515 let len = u32::from_be_bytes(
516 buf[prefix_offset + 1..prefix_offset + 5]
517 .try_into()
518 .unwrap(),
519 );
520 assert_eq!(len as usize, raw.len());
521 assert_eq!(&buf[prefix_offset + 5..prefix_offset + 5 + raw.len()], &raw);
522 assert_eq!(prefix_offset + 5 + raw.len(), buf.len());
523 }
524
525 #[test]
526 fn v1_omits_ref_list() {
527 let block_v0 = make_v0_block(Some(Program::from(vec![0x80])), vec![42]);
528 let buf_v0 = block_v0.to_bytes().unwrap();
529
530 let block_v1 = make_v1_block(Some(vec![0x80]));
531 let buf_v1 = block_v1.to_bytes().unwrap();
532
533 assert!(buf_v1.len() < buf_v0.len());
536 }
537
538 #[test]
539 fn v0_and_v1_same_hash_fields_before_generator() {
540 let block_v0 = make_v0_block(None, vec![]);
541 let block_v1 = make_v1_block(None);
542
543 assert_eq!(block_v0.header_hash(), block_v1.header_hash());
544 }
545
546 #[test]
547 fn v1_unvalidated_buffer_roundtrips() {
548 let garbage = vec![0xff; 1000];
549 let block = make_v1_block(Some(garbage.clone()));
550 let buf = block.to_bytes().unwrap();
551 let block2 = FullBlock::parse::<false>(&mut Cursor::new(&buf)).unwrap();
552 assert_eq!(
553 block2.transactions_generator_buffer.unwrap().to_vec(),
554 garbage
555 );
556 }
557
558 #[test]
564 fn high_prefix_bits_rejected() {
565 let v0_none = make_v0_block(None, vec![]).to_bytes().unwrap();
566 let v0_some = make_v0_block(Some(Program::from(vec![0x80])), vec![])
567 .to_bytes()
568 .unwrap();
569 let offset = v0_none
570 .iter()
571 .zip(v0_some.iter())
572 .position(|(a, b)| a != b)
573 .unwrap();
574 assert_eq!(v0_none[offset], 0b00);
575
576 let v1_none = make_v1_block(None).to_bytes().unwrap();
577 assert_eq!(v1_none[offset], 0b10);
578
579 for valid in [&v0_none, &v1_none] {
580 for bit in 2..8u8 {
581 let mut buf = valid.clone();
582 buf[offset] |= 1 << bit;
583 let err = FullBlock::parse::<false>(&mut Cursor::new(&buf))
584 .expect_err("high prefix bit must be rejected");
585 assert_eq!(err, Error::InvalidFullBlock);
586 }
587 }
588 }
589}