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<Bytes>,
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.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        // v0: 1 (prefix) + 1 (program "80") + 4 (ref_list count) + 4 (one u32) = 10 tail bytes
534        // v1: 1 (prefix) + 4 (length) + 1 (data) = 6 tail bytes
535        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    // The version flag is packed into the transactions_generator Option prefix
559    // byte. Only bit 0 (the Option flag) and bit 1 (the version) carry
560    // meaning. The high bits (2..=7) must be rejected, matching the strictness
561    // of a plain Option<> prefix in earlier protocol versions where this byte
562    // could only ever be 0 or 1.
563    #[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}