casper_types/block/
block_v2.rs

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/// A block after execution, with the resulting global state root hash. This is the core component
82/// of the Casper linear blockchain. Version 2.
83#[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    /// The block hash identifying this block.
89    pub(super) hash: BlockHash,
90    /// The header portion of the block.
91    pub(super) header: BlockHeaderV2,
92    /// The body portion of the block.
93    pub(super) body: BlockBodyV2,
94}
95
96impl BlockV2 {
97    // This method is not intended to be used by third party crates.
98    #[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    // This method is not intended to be used by third party crates.
140    #[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    /// Returns the `BlockHash` identifying this block.
147    pub fn hash(&self) -> &BlockHash {
148        &self.hash
149    }
150
151    /// Returns the block's header.
152    pub fn header(&self) -> &BlockHeaderV2 {
153        &self.header
154    }
155
156    /// Returns the block's header, consuming `self`.
157    pub fn take_header(self) -> BlockHeaderV2 {
158        self.header
159    }
160
161    /// Returns the block's body.
162    pub fn body(&self) -> &BlockBodyV2 {
163        &self.body
164    }
165
166    /// Returns the block's body, consuming `self`.
167    pub fn take_body(self) -> BlockBodyV2 {
168        self.body
169    }
170
171    /// Returns the parent block's hash.
172    pub fn parent_hash(&self) -> &BlockHash {
173        self.header.parent_hash()
174    }
175
176    /// Returns the root hash of global state after the deploys in this block have been executed.
177    pub fn state_root_hash(&self) -> &Digest {
178        self.header.state_root_hash()
179    }
180
181    /// Returns the hash of the block's body.
182    pub fn body_hash(&self) -> &Digest {
183        self.header.body_hash()
184    }
185
186    /// Returns a random bit needed for initializing a future era.
187    pub fn random_bit(&self) -> bool {
188        self.header.random_bit()
189    }
190
191    /// Returns a seed needed for initializing a future era.
192    pub fn accumulated_seed(&self) -> &Digest {
193        self.header.accumulated_seed()
194    }
195
196    /// Returns the `EraEnd` of a block if it is a switch block.
197    pub fn era_end(&self) -> Option<&EraEndV2> {
198        self.header.era_end()
199    }
200
201    /// Returns the timestamp from when the block was proposed.
202    pub fn timestamp(&self) -> Timestamp {
203        self.header.timestamp()
204    }
205
206    /// Returns the era ID in which this block was created.
207    pub fn era_id(&self) -> EraId {
208        self.header.era_id()
209    }
210
211    /// Returns the height of this block, i.e. the number of ancestors.
212    pub fn height(&self) -> u64 {
213        self.header.height()
214    }
215
216    /// Returns the protocol version of the network from when this block was created.
217    pub fn protocol_version(&self) -> ProtocolVersion {
218        self.header.protocol_version()
219    }
220
221    /// Returns `true` if this block is the last one in the current era.
222    pub fn is_switch_block(&self) -> bool {
223        self.header.is_switch_block()
224    }
225
226    /// Returns `true` if this block is the Genesis block, i.e. has height 0 and era 0.
227    pub fn is_genesis(&self) -> bool {
228        self.header.is_genesis()
229    }
230
231    /// Returns the public key of the validator which proposed the block.
232    pub fn proposer(&self) -> &PublicKey {
233        self.header.proposer()
234    }
235
236    /// List of identifiers for finality signatures for a particular past block.
237    pub fn rewarded_signatures(&self) -> &RewardedSignatures {
238        self.body.rewarded_signatures()
239    }
240
241    /// Returns the hashes of the transfer transactions within the block.
242    pub fn mint(&self) -> impl Iterator<Item = TransactionHash> {
243        self.body.mint()
244    }
245
246    /// Returns the hashes of the non-transfer, native transactions within the block.
247    pub fn auction(&self) -> impl Iterator<Item = TransactionHash> {
248        self.body.auction()
249    }
250
251    /// Returns the hashes of the install/upgrade wasm transactions within the block.
252    pub fn install_upgrade(&self) -> impl Iterator<Item = TransactionHash> {
253        self.body.install_upgrade()
254    }
255
256    /// Returns the hashes of the transactions filtered by lane id within the block.
257    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    /// Returns all of the transaction hashes in the order in which they were executed.
262    pub fn all_transactions(&self) -> impl Iterator<Item = &TransactionHash> {
263        self.body.all_transactions()
264    }
265
266    /// Returns a reference to the collection of mapped transactions.
267    pub fn transactions(&self) -> &BTreeMap<u8, Vec<TransactionHash>> {
268        self.body.transactions()
269    }
270
271    /// Returns the last relevant switch block hash.
272    pub fn last_switch_block_hash(&self) -> Option<BlockHash> {
273        self.header.last_switch_block_hash()
274    }
275
276    /// Returns `Ok` if and only if the block's provided block hash and body hash are identical to
277    /// those generated by hashing the appropriate input data.
278    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    // This method is not intended to be used by third party crates.
299    #[doc(hidden)]
300    #[cfg(feature = "json-schema")]
301    pub fn example() -> &'static Self {
302        &BLOCK_V2
303    }
304
305    /// Makes the block invalid, for testing purpose.
306    #[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}