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}