massa_consensus_exports/
export_active_block.rs

1use crate::{block_status::StorageOrBlock, error::ConsensusError};
2use massa_models::{
3    active_block::ActiveBlock,
4    block::{Block, BlockDeserializer, BlockDeserializerArgs, SecureShareBlock},
5    block_id::{BlockId, BlockIdDeserializer, BlockIdSerializer},
6    prehash::PreHashMap,
7    secure_share::{SecureShareDeserializer, SecureShareSerializer},
8};
9use massa_serialization::{
10    Deserializer, SerializeError, Serializer, U64VarIntDeserializer, U64VarIntSerializer,
11};
12use nom::branch::alt;
13use nom::{
14    bytes::complete::tag,
15    combinator::value,
16    error::{ContextError, ParseError},
17    multi::count,
18    sequence::{preceded, tuple},
19};
20use nom::{error::context, IResult, Parser};
21use serde::{Deserialize, Serialize};
22use std::ops::Bound::Included;
23
24/// Exportable version of `ActiveBlock`
25/// Fields that can be easily recomputed were left out
26#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct ExportActiveBlock {
28    /// The block.
29    pub block: SecureShareBlock,
30    /// one `(block id, period)` per thread ( if not genesis )
31    pub parents: Vec<(BlockId, u64)>,
32    /// for example has its fitness reached the given threshold
33    pub is_final: bool,
34}
35
36impl ExportActiveBlock {
37    /// conversion from active block to export active block
38    pub fn from_active_block(a_block: &ActiveBlock, storage_or_block: &StorageOrBlock) -> Self {
39        // TODO: if we decide that endorsements are separate, also gather endorsements here
40        ExportActiveBlock {
41            parents: a_block.parents.clone(),
42            is_final: a_block.is_final,
43            block: storage_or_block.clone_block(&a_block.block_id),
44        }
45    }
46
47    /// Parent block IDs must match the signed header (order and content).
48    /// Periods are not in the header and remain bootstrap metadata.
49    fn check_parents_match_header(&self, thread_count: u8) -> Result<(), String> {
50        let header_parents = &self.block.content.header.content.parents;
51        if self.parents.is_empty() {
52            if !header_parents.is_empty() {
53                return Err(format!(
54                    "ExportActiveBlock {} has empty parents but header lists {} parents",
55                    self.block.id,
56                    header_parents.len()
57                ));
58            }
59            return Ok(());
60        }
61        let expected_len = thread_count as usize;
62        if self.parents.len() != expected_len || header_parents.len() != expected_len {
63            return Err(format!(
64                "ExportActiveBlock {} parents length mismatch: export={}, header={}, expected={}",
65                self.block.id,
66                self.parents.len(),
67                header_parents.len(),
68                expected_len
69            ));
70        }
71        for (thread, ((export_parent, _), header_parent)) in
72            self.parents.iter().zip(header_parents.iter()).enumerate()
73        {
74            if export_parent != header_parent {
75                return Err(format!(
76                    "ExportActiveBlock {} parent id mismatch in thread {}: export={} header={}",
77                    self.block.id, thread, export_parent, header_parent
78                ));
79            }
80        }
81        Ok(())
82    }
83
84    /// consuming conversion from `ExportActiveBlock` to `ActiveBlock`
85    pub fn to_active_block(
86        self,
87        thread_count: u8,
88    ) -> Result<(ActiveBlock, StorageOrBlock), ConsensusError> {
89        // Also enforced in ExportActiveBlockDeserializer for the bootstrap wire
90        // path; kept here for ExportActiveBlock values that did not come from it
91        // (e.g. tests or other construction).
92        self.check_parents_match_header(thread_count)
93            .map_err(ConsensusError::ContainerInconsistency)?;
94
95        // create ActiveBlock
96        let active_block = ActiveBlock {
97            creator_address: self.block.content_creator_address,
98            block_id: self.block.id,
99            parents: self.parents.clone(),
100            children: vec![PreHashMap::default(); thread_count as usize], // will be computed once the full graph is available
101            descendants: Default::default(), // will be computed once the full graph is available
102            is_final: self.is_final,
103            slot: self.block.content.header.content.slot,
104            fitness: self.block.get_fitness(),
105            same_thread_parent_creator: None, // will be computed once the full graph is available
106        };
107
108        Ok((active_block, StorageOrBlock::Block(Box::new(self.block))))
109    }
110}
111
112/// Basic serializer of `ExportActiveBlock`
113#[derive(Default)]
114pub struct ExportActiveBlockSerializer {
115    sec_share_serializer: SecureShareSerializer,
116    period_serializer: U64VarIntSerializer,
117    block_id_serializer: BlockIdSerializer,
118}
119
120impl ExportActiveBlockSerializer {
121    /// Create a new `ExportActiveBlockSerializer`
122    pub fn new() -> Self {
123        ExportActiveBlockSerializer {
124            sec_share_serializer: SecureShareSerializer::new(),
125            period_serializer: U64VarIntSerializer::new(),
126            block_id_serializer: BlockIdSerializer::new(),
127        }
128    }
129}
130
131impl Serializer<ExportActiveBlock> for ExportActiveBlockSerializer {
132    fn serialize(
133        &self,
134        value: &ExportActiveBlock,
135        buffer: &mut Vec<u8>,
136    ) -> Result<(), SerializeError> {
137        // block
138        self.sec_share_serializer.serialize(&value.block, buffer)?;
139
140        // parents with periods
141        // note: there should be no parents for genesis blocks
142        buffer.push(u8::from(!value.parents.is_empty()));
143        for (hash, period) in value.parents.iter() {
144            self.block_id_serializer.serialize(hash, buffer)?;
145            self.period_serializer.serialize(period, buffer)?;
146        }
147
148        // finality
149        buffer.push(u8::from(value.is_final));
150
151        Ok(())
152    }
153}
154
155/// Basic deserializer of `ExportActiveBlock`
156pub struct ExportActiveBlockDeserializer {
157    sec_share_block_deserializer: SecureShareDeserializer<Block, BlockDeserializer>,
158    block_id_deserializer: BlockIdDeserializer,
159    period_deserializer: U64VarIntDeserializer,
160    thread_count: u8,
161}
162
163impl ExportActiveBlockDeserializer {
164    /// Create a new `ExportActiveBlockDeserializer`
165    // TODO: check if we can remove this?
166    #[allow(clippy::too_many_arguments)]
167    pub fn new(block_der_args: BlockDeserializerArgs) -> Self {
168        let thread_count = block_der_args.thread_count;
169        let chain_id = block_der_args.chain_id;
170        ExportActiveBlockDeserializer {
171            sec_share_block_deserializer: SecureShareDeserializer::new(
172                BlockDeserializer::new(block_der_args),
173                chain_id,
174            ),
175            block_id_deserializer: BlockIdDeserializer::new(),
176            period_deserializer: U64VarIntDeserializer::new(Included(0), Included(u64::MAX)),
177            thread_count,
178        }
179    }
180}
181
182impl Deserializer<ExportActiveBlock> for ExportActiveBlockDeserializer {
183    /// ## Example:
184    /// ```rust
185    /// use massa_consensus_exports::export_active_block::{ExportActiveBlock, ExportActiveBlockDeserializer, ExportActiveBlockSerializer};
186    /// use massa_models::{ledger::LedgerChanges, config::THREAD_COUNT, rolls::RollUpdates, block::{Block, BlockSerializer}, prehash::PreHashSet, endorsement::{Endorsement, EndorsementSerializer}, slot::Slot, secure_share::SecureShareContent};
187    /// use massa_models::block_id::BlockId;
188    /// use massa_models::block_header::{BlockHeader, BlockHeaderSerializer};
189    /// use massa_hash::Hash;
190    /// use std::collections::HashSet;
191    /// use massa_models::block::BlockDeserializerArgs;
192    /// use massa_models::config::CHAINID;
193    /// use massa_models::operation::{compute_operations_hash, OperationIdSerializer};
194    /// use massa_signature::KeyPair;
195    /// use massa_serialization::{Serializer, Deserializer, DeserializeError};
196    ///
197    /// let keypair = KeyPair::generate(0).unwrap();
198    /// let parents = (0..THREAD_COUNT)
199    ///     .map(|i| BlockId::generate_from_hash(Hash::compute_from(&[i])))
200    ///     .collect();
201    ///
202    /// // create block header
203    /// let orig_header = BlockHeader::new_verifiable(
204    ///     BlockHeader {
205    ///         current_version: 0,
206    ///         announced_version: None,
207    ///         slot: Slot::new(1, 1),
208    ///         parents,
209    ///         operation_merkle_root: compute_operations_hash(&[], &OperationIdSerializer::new()),
210    ///         endorsements: vec![
211    ///             Endorsement::new_verifiable(
212    ///                 Endorsement {
213    ///                     slot: Slot::new(1, 1),
214    ///                     index: 1,
215    ///                     endorsed_block: BlockId::generate_from_hash(Hash::compute_from(&[1])),
216    ///                 },
217    ///                 EndorsementSerializer::new(),
218    ///                 &keypair,
219    ///                 *CHAINID,
220    ///                 None
221    ///             )
222    ///             .unwrap(),
223    ///             Endorsement::new_verifiable(
224    ///                 Endorsement {
225    ///                     slot: Slot::new(1, 1),
226    ///                     index: 3,
227    ///                     endorsed_block: BlockId::generate_from_hash(Hash::compute_from(&[1])),
228    ///                 },
229    ///                 EndorsementSerializer::new(),
230    ///                 &keypair,
231    ///                 *CHAINID,
232    ///                 None
233    ///             )
234    ///             .unwrap(),
235    ///         ],
236    ///     denunciations: vec![],},
237    ///     BlockHeaderSerializer::new(),
238    ///     &keypair,
239    ///     *CHAINID,
240    ///     None
241    /// )
242    /// .unwrap();
243    ///
244    /// // create block
245    /// let orig_block = Block {
246    ///     header: orig_header,
247    ///     operations: Vec::new(),
248    /// };
249    ///
250    /// let full_block = Block::new_verifiable(orig_block, BlockSerializer::new(), &keypair, *CHAINID, None).unwrap();
251    /// let export_parents = full_block
252    ///     .content
253    ///     .header
254    ///     .content
255    ///     .parents
256    ///     .iter()
257    ///     .enumerate()
258    ///     .map(|(n, id)| (*id, n as u64))
259    ///     .collect();
260    /// let export_active_block = ExportActiveBlock {
261    ///    block: full_block.clone(),
262    ///    parents: export_parents,
263    ///    is_final: false,
264    /// };
265    ///
266    /// let mut serialized = Vec::new();
267    /// ExportActiveBlockSerializer::new().serialize(&export_active_block, &mut serialized).unwrap();
268    /// let args = BlockDeserializerArgs {
269    ///   thread_count: 32, max_operations_per_block: 16, endorsement_count: 1000,max_denunciations_per_block_header: 128,last_start_period: Some(0),chain_id: *CHAINID};
270    /// let (rest, export_deserialized) = ExportActiveBlockDeserializer::new(args).deserialize::<DeserializeError>(&serialized).unwrap();
271    /// assert_eq!(export_deserialized.block.id, export_active_block.block.id);
272    /// assert_eq!(export_deserialized.block.serialized_data, export_active_block.block.serialized_data);
273    /// assert_eq!(export_deserialized.parents, export_active_block.parents);
274    /// assert_eq!(rest.len(), 0);
275    /// ```
276    fn deserialize<'a, E: ParseError<&'a [u8]> + ContextError<&'a [u8]>>(
277        &self,
278        buffer: &'a [u8],
279    ) -> IResult<&'a [u8], ExportActiveBlock, E> {
280        let (rest, (block, parents, is_final)) = context(
281            "Failed ExportActiveBlock deserialization",
282            tuple((
283                // block
284                context("Failed block deserialization", |input| {
285                    self.sec_share_block_deserializer.deserialize(input)
286                }),
287                // parents
288                context(
289                    "Failed parents deserialization",
290                    alt((
291                        value(Vec::new(), tag(&[0])),
292                        preceded(
293                            tag(&[1]),
294                            count(
295                                tuple((
296                                    context("Failed block_id deserialization", |input| {
297                                        self.block_id_deserializer.deserialize(input)
298                                    }),
299                                    context("Failed period deserialization", |input| {
300                                        self.period_deserializer.deserialize(input)
301                                    }),
302                                )),
303                                self.thread_count as usize,
304                            ),
305                        ),
306                    )),
307                ),
308                // finality
309                context(
310                    "Failed is_final deserialization",
311                    alt((value(true, tag(&[1])), value(false, tag(&[0])))),
312                ),
313            )),
314        )
315        .parse(buffer)?;
316
317        let export = ExportActiveBlock {
318            block,
319            parents,
320            is_final,
321        };
322        if export
323            .check_parents_match_header(self.thread_count)
324            .is_err()
325        {
326            return Err(nom::Err::Failure(ContextError::add_context(
327                rest,
328                "ExportActiveBlock parents must match signed header parents",
329                ParseError::from_error_kind(rest, nom::error::ErrorKind::Fail),
330            )));
331        }
332        Ok((rest, export))
333    }
334}