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