Skip to main content

chia_protocol/
fullblock.rs

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// Similar to ProofOfSpace, we use unused bits in the Option<> prefix byte
17// for transactions_generator to encode a version flag. Bit 1 (0b10) indicates
18// the "raw bytes" format where the generator is serialized as length-prefixed
19// bytes (like Bytes) instead of a self-describing CLVM Program, and
20// transactions_generator_ref_list is omitted entirely.
21#[streamable(no_streamable)]
22pub struct FullBlock {
23    finished_sub_slots: Vec<EndOfSubSlotBundle>,
24    reward_chain_block: RewardChainBlock,
25    challenge_chain_sp_proof: Option<VDFProof>, // # If not first sp in sub-slot
26    challenge_chain_ip_proof: VDFProof,
27    reward_chain_sp_proof: Option<VDFProof>, // # If not first sp in sub-slot
28    reward_chain_ip_proof: VDFProof,
29    infused_challenge_chain_ip_proof: Option<VDFProof>, // # Iff deficit < 4
30    foliage: Foliage,                                   // # Reward chain foliage data
31    foliage_transaction_block: Option<FoliageTransactionBlock>, // # Reward chain foliage data (tx block)
32    transactions_info: Option<TransactionsInfo>, // Reward chain foliage data (tx block additional)
33    transactions_generator: Option<Program>,     // Program that generates transactions
34    transactions_generator_ref_list: Vec<u32>, // List of block heights of previous generators referenced in this block
35
36    // Raw generator bytes, only used when version == 1. Mutually exclusive
37    // with transactions_generator and transactions_generator_ref_list.
38    transactions_generator_buffer: Option<Vec<u8>>,
39
40    // 0 = legacy format (Program serialization + ref_list)
41    // 1 = raw bytes format (length-prefixed bytes, ref_list omitted)
42    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        // v0: 1 (prefix) + 1 (program "80") + 4 (ref_list count) + 4 (one u32) = 10 tail bytes
530        // v1: 1 (prefix) + 4 (length) + 1 (data) = 6 tail bytes
531        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    // The version flag is packed into the transactions_generator Option prefix
552    // byte. Only bit 0 (the Option flag) and bit 1 (the version) carry
553    // meaning. The high bits (2..=7) must be rejected, matching the strictness
554    // of a plain Option<> prefix in earlier protocol versions where this byte
555    // could only ever be 0 or 1.
556    #[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}