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<Vec<u8>>,
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.len() as u32).update_digest(digest);
63                    digest.update(buf);
64                }
65            }
66        } else {
67            digest.update(b"invalid-unfinished-block-version");
68        }
69    }
70
71    fn stream(&self, out: &mut Vec<u8>) -> Result<()> {
72        self.finished_sub_slots.stream(out)?;
73        self.reward_chain_block.stream(out)?;
74        self.challenge_chain_sp_proof.stream(out)?;
75        self.reward_chain_sp_proof.stream(out)?;
76        self.foliage.stream(out)?;
77        self.foliage_transaction_block.stream(out)?;
78        self.transactions_info.stream(out)?;
79
80        if self.version == 0 {
81            self.transactions_generator.stream(out)?;
82            self.transactions_generator_ref_list.stream(out)?;
83        } else if self.version == 1 {
84            match &self.transactions_generator_buffer {
85                None => {
86                    0b10_u8.stream(out)?;
87                }
88                Some(buf) => {
89                    0b11_u8.stream(out)?;
90                    (buf.len() as u32).stream(out)?;
91                    out.extend_from_slice(buf);
92                }
93            }
94        } else {
95            return Err(Error::InvalidUnfinishedBlock);
96        }
97        Ok(())
98    }
99
100    fn parse<const TRUSTED: bool>(input: &mut Cursor<&[u8]>) -> Result<Self> {
101        let finished_sub_slots = <Vec<EndOfSubSlotBundle> as Streamable>::parse::<TRUSTED>(input)?;
102        let reward_chain_block =
103            <RewardChainBlockUnfinished as Streamable>::parse::<TRUSTED>(input)?;
104        let challenge_chain_sp_proof = <Option<VDFProof> as Streamable>::parse::<TRUSTED>(input)?;
105        let reward_chain_sp_proof = <Option<VDFProof> as Streamable>::parse::<TRUSTED>(input)?;
106        let foliage = <Foliage as Streamable>::parse::<TRUSTED>(input)?;
107        let foliage_transaction_block =
108            <Option<FoliageTransactionBlock> as Streamable>::parse::<TRUSTED>(input)?;
109        let transactions_info = <Option<TransactionsInfo> as Streamable>::parse::<TRUSTED>(input)?;
110
111        let prefix = <u8 as Streamable>::parse::<TRUSTED>(input)?;
112        let version = prefix >> 1;
113        let has_generator = (prefix & 1) != 0;
114
115        if version == 0 {
116            let transactions_generator = if has_generator {
117                Some(<Program as Streamable>::parse::<TRUSTED>(input)?)
118            } else {
119                None
120            };
121            let transactions_generator_ref_list =
122                <Vec<u32> as Streamable>::parse::<TRUSTED>(input)?;
123
124            Ok(UnfinishedBlock {
125                finished_sub_slots,
126                reward_chain_block,
127                challenge_chain_sp_proof,
128                reward_chain_sp_proof,
129                foliage,
130                foliage_transaction_block,
131                transactions_info,
132                transactions_generator,
133                transactions_generator_ref_list,
134                transactions_generator_buffer: None,
135                version,
136            })
137        } else if version == 1 {
138            let transactions_generator_buffer = if has_generator {
139                let bytes = <Bytes as Streamable>::parse::<TRUSTED>(input)?;
140                Some(bytes.into_inner())
141            } else {
142                None
143            };
144
145            Ok(UnfinishedBlock {
146                finished_sub_slots,
147                reward_chain_block,
148                challenge_chain_sp_proof,
149                reward_chain_sp_proof,
150                foliage,
151                foliage_transaction_block,
152                transactions_info,
153                transactions_generator: None,
154                transactions_generator_ref_list: vec![],
155                transactions_generator_buffer,
156                version,
157            })
158        } else {
159            Err(Error::InvalidUnfinishedBlock)
160        }
161    }
162}
163
164impl UnfinishedBlock {
165    pub fn prev_header_hash(&self) -> Bytes32 {
166        self.foliage.prev_block_hash
167    }
168
169    pub fn partial_hash(&self) -> Bytes32 {
170        self.reward_chain_block.hash().into()
171    }
172
173    pub fn is_transaction_block(&self) -> bool {
174        self.foliage.foliage_transaction_block_hash.is_some()
175    }
176
177    pub fn total_iters(&self) -> u128 {
178        self.reward_chain_block.total_iters
179    }
180}
181
182#[cfg(feature = "py-bindings")]
183use chia_traits::ChiaToPython;
184#[cfg(feature = "py-bindings")]
185use pyo3::prelude::*;
186
187#[cfg(feature = "py-bindings")]
188#[pymethods]
189impl UnfinishedBlock {
190    #[getter]
191    #[pyo3(name = "prev_header_hash")]
192    fn py_prev_header_hash(&self) -> Bytes32 {
193        self.prev_header_hash()
194    }
195
196    #[getter]
197    #[pyo3(name = "partial_hash")]
198    fn py_partial_hash(&self) -> Bytes32 {
199        self.partial_hash()
200    }
201
202    #[pyo3(name = "is_transaction_block")]
203    fn py_is_transaction_block(&self) -> bool {
204        self.is_transaction_block()
205    }
206
207    #[getter]
208    #[pyo3(name = "total_iters")]
209    fn py_total_iters<'a>(&self, py: Python<'a>) -> PyResult<Bound<'a, PyAny>> {
210        ChiaToPython::to_python(&self.total_iters(), py)
211    }
212}
213
214#[cfg(test)]
215mod tests {
216    use super::*;
217    use crate::{FoliageBlockData, PoolTarget, ProofOfSpace};
218    use chia_bls::{G1Element, G2Element};
219    use rstest::rstest;
220
221    fn make_proof_of_space() -> ProofOfSpace {
222        ProofOfSpace::new(
223            Bytes32::default(),
224            Some(G1Element::default()),
225            None,
226            G1Element::default(),
227            0,
228            0,
229            0,
230            0,
231            32,
232            Bytes::from(vec![0x80]),
233        )
234    }
235
236    fn make_reward_chain_block_unfinished() -> RewardChainBlockUnfinished {
237        RewardChainBlockUnfinished::new(
238            1,
239            0,
240            Bytes32::default(),
241            make_proof_of_space(),
242            None,
243            G2Element::default(),
244            None,
245            G2Element::default(),
246        )
247    }
248
249    fn make_foliage() -> Foliage {
250        let pool_target = PoolTarget::new(Bytes32::default(), 0);
251        let foliage_block_data = FoliageBlockData::new(
252            Bytes32::default(),
253            pool_target,
254            Some(G2Element::default()),
255            Bytes32::default(),
256            Bytes32::default(),
257        );
258        Foliage::new(
259            Bytes32::default(),
260            Bytes32::default(),
261            foliage_block_data,
262            G2Element::default(),
263            None,
264            None,
265        )
266    }
267
268    fn make_v0_block(generator: Option<Program>, ref_list: Vec<u32>) -> UnfinishedBlock {
269        UnfinishedBlock::new(
270            vec![],
271            make_reward_chain_block_unfinished(),
272            None,
273            None,
274            make_foliage(),
275            None,
276            None,
277            generator,
278            ref_list,
279            None,
280            0,
281        )
282    }
283
284    fn make_v1_block(buffer: Option<Vec<u8>>) -> UnfinishedBlock {
285        UnfinishedBlock::new(
286            vec![],
287            make_reward_chain_block_unfinished(),
288            None,
289            None,
290            make_foliage(),
291            None,
292            None,
293            None,
294            vec![],
295            buffer,
296            1,
297        )
298    }
299
300    #[test]
301    fn v0_no_generator_roundtrip() {
302        let block = make_v0_block(None, vec![]);
303        let buf = block.to_bytes().unwrap();
304        let block2 = UnfinishedBlock::parse::<false>(&mut Cursor::new(&buf)).unwrap();
305
306        assert_eq!(block2.version, 0);
307        assert!(block2.transactions_generator.is_none());
308        assert!(block2.transactions_generator_ref_list.is_empty());
309        assert!(block2.transactions_generator_buffer.is_none());
310        assert_eq!(block2.to_bytes().unwrap(), buf);
311    }
312
313    #[test]
314    fn v0_with_generator_roundtrip() {
315        let generator = Program::from(vec![0xff, 0x01, 0x80]);
316        let block = make_v0_block(Some(generator.clone()), vec![100, 200]);
317        let buf = block.to_bytes().unwrap();
318        let block2 = UnfinishedBlock::parse::<false>(&mut Cursor::new(&buf)).unwrap();
319
320        assert_eq!(block2.version, 0);
321        assert_eq!(
322            block2.transactions_generator.as_ref().unwrap().as_ref(),
323            generator.as_ref()
324        );
325        assert_eq!(block2.transactions_generator_ref_list, vec![100, 200]);
326        assert!(block2.transactions_generator_buffer.is_none());
327        assert_eq!(block2.to_bytes().unwrap(), buf);
328    }
329
330    #[test]
331    fn v1_no_generator_roundtrip() {
332        let block = make_v1_block(None);
333        let buf = block.to_bytes().unwrap();
334        let block2 = UnfinishedBlock::parse::<false>(&mut Cursor::new(&buf)).unwrap();
335
336        assert_eq!(block2.version, 1);
337        assert!(block2.transactions_generator.is_none());
338        assert!(block2.transactions_generator_ref_list.is_empty());
339        assert!(block2.transactions_generator_buffer.is_none());
340        assert_eq!(block2.to_bytes().unwrap(), buf);
341    }
342
343    #[test]
344    fn v1_with_buffer_roundtrip() {
345        let raw = vec![0xde, 0xad, 0xbe, 0xef, 0xca, 0xfe];
346        let block = make_v1_block(Some(raw.clone()));
347        let buf = block.to_bytes().unwrap();
348        let block2 = UnfinishedBlock::parse::<false>(&mut Cursor::new(&buf)).unwrap();
349
350        assert_eq!(block2.version, 1);
351        assert!(block2.transactions_generator.is_none());
352        assert!(block2.transactions_generator_ref_list.is_empty());
353        assert_eq!(block2.transactions_generator_buffer.as_ref().unwrap(), &raw);
354        assert_eq!(block2.to_bytes().unwrap(), buf);
355    }
356
357    #[rstest]
358    #[case::v0(0, 0b00, 0b01)]
359    #[case::v1(1, 0b10, 0b11)]
360    fn prefix_byte_encoding(
361        #[case] version: u8,
362        #[case] expected_none: u8,
363        #[case] expected_some: u8,
364    ) {
365        let (buf_none, buf_some) = if version == 0 {
366            (
367                make_v0_block(None, vec![]).to_bytes().unwrap(),
368                make_v0_block(Some(Program::from(vec![0x80])), vec![])
369                    .to_bytes()
370                    .unwrap(),
371            )
372        } else {
373            (
374                make_v1_block(None).to_bytes().unwrap(),
375                make_v1_block(Some(vec![0x80])).to_bytes().unwrap(),
376            )
377        };
378
379        let prefix_offset = buf_none
380            .iter()
381            .zip(buf_some.iter())
382            .position(|(a, b)| a != b)
383            .unwrap();
384
385        assert_eq!(buf_none[prefix_offset], expected_none);
386        assert_eq!(buf_some[prefix_offset], expected_some);
387    }
388
389    #[test]
390    fn v1_generator_has_length_prefix() {
391        let raw = vec![0xca, 0xfe, 0xba, 0xbe];
392        let block = make_v1_block(Some(raw.clone()));
393        let buf = block.to_bytes().unwrap();
394
395        let block_empty = make_v1_block(None);
396        let buf_empty = block_empty.to_bytes().unwrap();
397
398        let prefix_offset = buf
399            .iter()
400            .zip(buf_empty.iter())
401            .position(|(a, b)| a != b)
402            .unwrap();
403
404        assert_eq!(buf[prefix_offset], 0b11);
405        let len = u32::from_be_bytes(
406            buf[prefix_offset + 1..prefix_offset + 5]
407                .try_into()
408                .unwrap(),
409        );
410        assert_eq!(len as usize, raw.len());
411        assert_eq!(&buf[prefix_offset + 5..prefix_offset + 5 + raw.len()], &raw);
412        assert_eq!(prefix_offset + 5 + raw.len(), buf.len());
413    }
414
415    #[test]
416    fn v1_omits_ref_list() {
417        let block_v0 = make_v0_block(Some(Program::from(vec![0x80])), vec![42]);
418        let buf_v0 = block_v0.to_bytes().unwrap();
419
420        let block_v1 = make_v1_block(Some(vec![0x80]));
421        let buf_v1 = block_v1.to_bytes().unwrap();
422
423        assert!(buf_v1.len() < buf_v0.len());
424    }
425
426    #[test]
427    fn v1_unvalidated_buffer_roundtrips() {
428        let garbage = vec![0xff; 1000];
429        let block = make_v1_block(Some(garbage.clone()));
430        let buf = block.to_bytes().unwrap();
431        let block2 = UnfinishedBlock::parse::<false>(&mut Cursor::new(&buf)).unwrap();
432        assert_eq!(block2.transactions_generator_buffer.unwrap(), garbage);
433    }
434
435    // The version flag is packed into the transactions_generator Option prefix
436    // byte. Only bit 0 (the Option flag) and bit 1 (the version) carry
437    // meaning. The high bits (2..=7) must be rejected, matching the strictness
438    // of a plain Option<> prefix in earlier protocol versions where this byte
439    // could only ever be 0 or 1.
440    #[test]
441    fn high_prefix_bits_rejected() {
442        let v0_none = make_v0_block(None, vec![]).to_bytes().unwrap();
443        let v0_some = make_v0_block(Some(Program::from(vec![0x80])), vec![])
444            .to_bytes()
445            .unwrap();
446        let offset = v0_none
447            .iter()
448            .zip(v0_some.iter())
449            .position(|(a, b)| a != b)
450            .unwrap();
451        assert_eq!(v0_none[offset], 0b00);
452
453        let v1_none = make_v1_block(None).to_bytes().unwrap();
454        assert_eq!(v1_none[offset], 0b10);
455
456        for valid in [&v0_none, &v1_none] {
457            for bit in 2..8u8 {
458                let mut buf = valid.clone();
459                buf[offset] |= 1 << bit;
460                let err = UnfinishedBlock::parse::<false>(&mut Cursor::new(&buf))
461                    .expect_err("high prefix bit must be rejected");
462                assert_eq!(err, Error::InvalidUnfinishedBlock);
463            }
464        }
465    }
466}