1use alloc::{boxed::Box, collections::BTreeMap, vec::Vec};
2
3use core::{
4 convert::TryFrom,
5 fmt::{self, Display, Formatter},
6};
7#[cfg(feature = "datasize")]
8use datasize::DataSize;
9#[cfg(feature = "json-schema")]
10use once_cell::sync::Lazy;
11#[cfg(feature = "json-schema")]
12use schemars::JsonSchema;
13#[cfg(any(feature = "std", test))]
14use serde::{Deserialize, Serialize};
15
16#[cfg(any(feature = "once_cell", test))]
17use once_cell::sync::OnceCell;
18
19use super::{Block, BlockBodyV2, BlockConversionError, RewardedSignatures};
20#[cfg(any(all(feature = "std", feature = "testing"), test))]
21use crate::testing::TestRng;
22use crate::{
23 bytesrepr::{self, FromBytes, ToBytes},
24 transaction::TransactionHash,
25 BlockHash, BlockHeaderV2, BlockValidationError, Digest, EraEndV2, EraId, ProtocolVersion,
26 PublicKey, Timestamp,
27};
28#[cfg(feature = "json-schema")]
29use crate::{TransactionV1Hash, AUCTION_LANE_ID, INSTALL_UPGRADE_LANE_ID, MINT_LANE_ID};
30
31#[cfg(feature = "json-schema")]
32static BLOCK_V2: Lazy<BlockV2> = Lazy::new(|| {
33 let parent_hash = BlockHash::new(Digest::from([7; Digest::LENGTH]));
34 let parent_seed = Digest::from([9; Digest::LENGTH]);
35 let state_root_hash = Digest::from([8; Digest::LENGTH]);
36 let random_bit = true;
37 let era_end = Some(EraEndV2::example().clone());
38 let timestamp = *Timestamp::example();
39 let era_id = EraId::from(1);
40 let height = 10;
41 let protocol_version = ProtocolVersion::V1_0_0;
42 let secret_key = crate::SecretKey::example();
43 let proposer = PublicKey::from(secret_key);
44 let mint_hashes = vec![TransactionHash::V1(TransactionV1Hash::new(Digest::from(
45 [20; Digest::LENGTH],
46 )))];
47 let auction_hashes = vec![TransactionHash::V1(TransactionV1Hash::new(Digest::from(
48 [21; Digest::LENGTH],
49 )))];
50 let installer_upgrader_hashes = vec![TransactionHash::V1(TransactionV1Hash::new(
51 Digest::from([22; Digest::LENGTH]),
52 ))];
53 let transactions = {
54 let mut ret = BTreeMap::new();
55 ret.insert(MINT_LANE_ID, mint_hashes);
56 ret.insert(AUCTION_LANE_ID, auction_hashes);
57 ret.insert(INSTALL_UPGRADE_LANE_ID, installer_upgrader_hashes);
58 ret
59 };
60 let rewarded_signatures = RewardedSignatures::default();
61 let current_gas_price = 1u8;
62 let last_switch_block_hash = BlockHash::new(Digest::from([10; Digest::LENGTH]));
63 BlockV2::new(
64 parent_hash,
65 parent_seed,
66 state_root_hash,
67 random_bit,
68 era_end,
69 timestamp,
70 era_id,
71 height,
72 protocol_version,
73 proposer,
74 transactions,
75 rewarded_signatures,
76 current_gas_price,
77 Some(last_switch_block_hash),
78 )
79});
80
81#[cfg_attr(feature = "datasize", derive(DataSize))]
84#[cfg_attr(any(feature = "std", test), derive(Serialize, Deserialize))]
85#[derive(Clone, Debug, PartialEq, Eq)]
86#[cfg_attr(feature = "json-schema", derive(JsonSchema))]
87pub struct BlockV2 {
88 pub(super) hash: BlockHash,
90 pub(super) header: BlockHeaderV2,
92 pub(super) body: BlockBodyV2,
94}
95
96impl BlockV2 {
97 #[doc(hidden)]
99 #[allow(clippy::too_many_arguments)]
100 pub fn new(
101 parent_hash: BlockHash,
102 parent_seed: Digest,
103 state_root_hash: Digest,
104 random_bit: bool,
105 era_end: Option<EraEndV2>,
106 timestamp: Timestamp,
107 era_id: EraId,
108 height: u64,
109 protocol_version: ProtocolVersion,
110 proposer: PublicKey,
111 transactions: BTreeMap<u8, Vec<TransactionHash>>,
112 rewarded_signatures: RewardedSignatures,
113 current_gas_price: u8,
114 last_switch_block_hash: Option<BlockHash>,
115 ) -> Self {
116 let body = BlockBodyV2::new(transactions, rewarded_signatures);
117 let body_hash = body.hash();
118 let accumulated_seed = Digest::hash_pair(parent_seed, [random_bit as u8]);
119 let header = BlockHeaderV2::new(
120 parent_hash,
121 state_root_hash,
122 body_hash,
123 random_bit,
124 accumulated_seed,
125 era_end,
126 timestamp,
127 era_id,
128 height,
129 protocol_version,
130 proposer,
131 current_gas_price,
132 last_switch_block_hash,
133 #[cfg(any(feature = "once_cell", test))]
134 OnceCell::new(),
135 );
136 Self::new_from_header_and_body(header, body)
137 }
138
139 #[doc(hidden)]
141 pub fn new_from_header_and_body(header: BlockHeaderV2, body: BlockBodyV2) -> Self {
142 let hash = header.block_hash();
143 BlockV2 { hash, header, body }
144 }
145
146 pub fn hash(&self) -> &BlockHash {
148 &self.hash
149 }
150
151 pub fn header(&self) -> &BlockHeaderV2 {
153 &self.header
154 }
155
156 pub fn take_header(self) -> BlockHeaderV2 {
158 self.header
159 }
160
161 pub fn body(&self) -> &BlockBodyV2 {
163 &self.body
164 }
165
166 pub fn take_body(self) -> BlockBodyV2 {
168 self.body
169 }
170
171 pub fn parent_hash(&self) -> &BlockHash {
173 self.header.parent_hash()
174 }
175
176 pub fn state_root_hash(&self) -> &Digest {
178 self.header.state_root_hash()
179 }
180
181 pub fn body_hash(&self) -> &Digest {
183 self.header.body_hash()
184 }
185
186 pub fn random_bit(&self) -> bool {
188 self.header.random_bit()
189 }
190
191 pub fn accumulated_seed(&self) -> &Digest {
193 self.header.accumulated_seed()
194 }
195
196 pub fn era_end(&self) -> Option<&EraEndV2> {
198 self.header.era_end()
199 }
200
201 pub fn timestamp(&self) -> Timestamp {
203 self.header.timestamp()
204 }
205
206 pub fn era_id(&self) -> EraId {
208 self.header.era_id()
209 }
210
211 pub fn height(&self) -> u64 {
213 self.header.height()
214 }
215
216 pub fn protocol_version(&self) -> ProtocolVersion {
218 self.header.protocol_version()
219 }
220
221 pub fn is_switch_block(&self) -> bool {
223 self.header.is_switch_block()
224 }
225
226 pub fn is_genesis(&self) -> bool {
228 self.header.is_genesis()
229 }
230
231 pub fn proposer(&self) -> &PublicKey {
233 self.header.proposer()
234 }
235
236 pub fn rewarded_signatures(&self) -> &RewardedSignatures {
238 self.body.rewarded_signatures()
239 }
240
241 pub fn mint(&self) -> impl Iterator<Item = TransactionHash> {
243 self.body.mint()
244 }
245
246 pub fn auction(&self) -> impl Iterator<Item = TransactionHash> {
248 self.body.auction()
249 }
250
251 pub fn install_upgrade(&self) -> impl Iterator<Item = TransactionHash> {
253 self.body.install_upgrade()
254 }
255
256 pub fn transactions_by_lane_id(&self, lane_id: u8) -> impl Iterator<Item = TransactionHash> {
258 self.body.transaction_by_lane(lane_id)
259 }
260
261 pub fn all_transactions(&self) -> impl Iterator<Item = &TransactionHash> {
263 self.body.all_transactions()
264 }
265
266 pub fn transactions(&self) -> &BTreeMap<u8, Vec<TransactionHash>> {
268 self.body.transactions()
269 }
270
271 pub fn last_switch_block_hash(&self) -> Option<BlockHash> {
273 self.header.last_switch_block_hash()
274 }
275
276 pub fn verify(&self) -> Result<(), BlockValidationError> {
279 let actual_block_header_hash = self.header().block_hash();
280 if *self.hash() != actual_block_header_hash {
281 return Err(BlockValidationError::UnexpectedBlockHash {
282 block: Box::new(Block::V2(self.clone())),
283 actual_block_hash: actual_block_header_hash,
284 });
285 }
286
287 let actual_block_body_hash = self.body.hash();
288 if *self.header.body_hash() != actual_block_body_hash {
289 return Err(BlockValidationError::UnexpectedBodyHash {
290 block: Box::new(Block::V2(self.clone())),
291 actual_block_body_hash,
292 });
293 }
294
295 Ok(())
296 }
297
298 #[doc(hidden)]
300 #[cfg(feature = "json-schema")]
301 pub fn example() -> &'static Self {
302 &BLOCK_V2
303 }
304
305 #[cfg(any(all(feature = "std", feature = "testing"), test))]
307 pub fn make_invalid(self, rng: &mut TestRng) -> Self {
308 let block = BlockV2 {
309 hash: BlockHash::random(rng),
310 ..self
311 };
312
313 assert!(block.verify().is_err());
314 block
315 }
316}
317
318impl Display for BlockV2 {
319 fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
320 write!(
321 formatter,
322 "executed block #{}, {}, timestamp {}, {}, parent {}, post-state hash {}, body hash \
323 {}, random bit {}, protocol version: {}",
324 self.height(),
325 self.hash(),
326 self.timestamp(),
327 self.era_id(),
328 self.parent_hash().inner(),
329 self.state_root_hash(),
330 self.body_hash(),
331 self.random_bit(),
332 self.protocol_version()
333 )?;
334 if let Some(era_end) = self.era_end() {
335 write!(formatter, ", era_end: {}", era_end)?;
336 }
337 Ok(())
338 }
339}
340
341impl ToBytes for BlockV2 {
342 fn write_bytes(&self, writer: &mut Vec<u8>) -> Result<(), bytesrepr::Error> {
343 self.hash.write_bytes(writer)?;
344 self.header.write_bytes(writer)?;
345 self.body.write_bytes(writer)
346 }
347
348 fn to_bytes(&self) -> Result<Vec<u8>, bytesrepr::Error> {
349 let mut buffer = bytesrepr::allocate_buffer(self)?;
350 self.write_bytes(&mut buffer)?;
351 Ok(buffer)
352 }
353
354 fn serialized_length(&self) -> usize {
355 self.hash.serialized_length()
356 + self.header.serialized_length()
357 + self.body.serialized_length()
358 }
359}
360
361impl FromBytes for BlockV2 {
362 fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> {
363 let (hash, remainder) = BlockHash::from_bytes(bytes)?;
364 let (header, remainder) = BlockHeaderV2::from_bytes(remainder)?;
365 let (body, remainder) = BlockBodyV2::from_bytes(remainder)?;
366 let block = BlockV2 { hash, header, body };
367 Ok((block, remainder))
368 }
369}
370
371impl TryFrom<Block> for BlockV2 {
372 type Error = BlockConversionError;
373
374 fn try_from(value: Block) -> Result<BlockV2, BlockConversionError> {
375 match value {
376 Block::V2(v2) => Ok(v2),
377 _ => Err(BlockConversionError::DifferentVersion {
378 expected_version: 2,
379 }),
380 }
381 }
382}
383
384#[cfg(test)]
385mod tests {
386 use crate::TestBlockBuilder;
387
388 use super::*;
389
390 #[test]
391 fn bytesrepr_roundtrip() {
392 let rng = &mut TestRng::new();
393 let block = TestBlockBuilder::new().build(rng);
394 bytesrepr::test_serialization_roundtrip(&block);
395 }
396
397 #[test]
398 fn block_check_bad_body_hash_sad_path() {
399 let rng = &mut TestRng::new();
400
401 let mut block = TestBlockBuilder::new().build(rng);
402 let bogus_block_body_hash = Digest::hash([0xde, 0xad, 0xbe, 0xef]);
403 block.header.set_body_hash(bogus_block_body_hash);
404 block.hash = block.header.block_hash();
405
406 let expected_error = BlockValidationError::UnexpectedBodyHash {
407 block: Box::new(Block::V2(block.clone())),
408 actual_block_body_hash: block.body.hash(),
409 };
410 assert_eq!(block.verify(), Err(expected_error));
411 }
412
413 #[test]
414 fn block_check_bad_block_hash_sad_path() {
415 let rng = &mut TestRng::new();
416
417 let mut block = TestBlockBuilder::new().build(rng);
418 let bogus_block_hash = BlockHash::from(Digest::hash([0xde, 0xad, 0xbe, 0xef]));
419 block.hash = bogus_block_hash;
420
421 let expected_error = BlockValidationError::UnexpectedBlockHash {
422 block: Box::new(Block::V2(block.clone())),
423 actual_block_hash: block.header.block_hash(),
424 };
425 assert_eq!(block.verify(), Err(expected_error));
426 }
427}