Skip to main content

chia_protocol/
unfinished_block.rs

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// Similar to FullBlock, we use unused bits in the Option<> prefix byte
16// for transactions_generator to encode a version flag. Bit 1 (0b10) indicates
17// the "raw bytes" format where the generator is serialized as length-prefixed
18// bytes (like Bytes) instead of a self-describing CLVM Program, and
19// transactions_generator_ref_list is omitted entirely.
20#[streamable(no_streamable)]
21pub struct UnfinishedBlock {
22    // Full block, without the final VDFs
23    finished_sub_slots: Vec<EndOfSubSlotBundle>, // If first sb
24    reward_chain_block: RewardChainBlockUnfinished, // Reward chain trunk data
25    challenge_chain_sp_proof: Option<VDFProof>,  // If not first sp in sub-slot
26    reward_chain_sp_proof: Option<VDFProof>,     // If not first sp in sub-slot
27    foliage: Foliage,                            // Reward chain foliage data
28    foliage_transaction_block: Option<FoliageTransactionBlock>, // Reward chain foliage data (tx block)
29    transactions_info: Option<TransactionsInfo>, // Reward chain foliage data (tx block additional)
30    transactions_generator: Option<Program>,     // Program that generates transactions
31    transactions_generator_ref_list: Vec<u32>, // List of block heights of previous generators referenced in this block
32
33    // Raw generator bytes, only used when version == 1. Mutually exclusive
34    // with transactions_generator and transactions_generator_ref_list.
35    transactions_generator_buffer: Option<Bytes>,
36
37    // 0 = legacy format (Program serialization + ref_list)
38    // 1 = raw bytes format (length-prefixed bytes, ref_list omitted)
39    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    // The version flag is packed into the transactions_generator Option prefix
443    // byte. Only bit 0 (the Option flag) and bit 1 (the version) carry
444    // meaning. The high bits (2..=7) must be rejected, matching the strictness
445    // of a plain Option<> prefix in earlier protocol versions where this byte
446    // could only ever be 0 or 1.
447    #[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}