casper_client/
validation.rs

1use thiserror::Error;
2
3use casper_hashing::Digest;
4use casper_types::bytesrepr;
5
6use crate::{
7    rpcs::{common::BlockIdentifier, results::GetBlockResult},
8    types::{self, Block, BlockHash},
9};
10
11/// Error that can be returned when validating data returned from a JSON-RPC method.
12#[derive(Error, Debug)]
13pub enum ValidateResponseError {
14    /// Failed to marshall value.
15    #[error("failed to marshall value {0}")]
16    BytesRepr(bytesrepr::Error),
17
18    /// Error from serde.
19    #[error(transparent)]
20    Serde(#[from] serde_json::Error),
21
22    /// Failed to parse JSON.
23    #[error("validate response failed to parse")]
24    ValidateResponseFailedToParse,
25
26    /// The body hash in the header is not the same as the hash of the body of the block.
27    #[error(
28        "block header has incorrect body hash. \
29         actual block body hash: {actual_block_body_hash}, \
30         block: {block}"
31    )]
32    BodyHashMismatch {
33        /// The `Block` with the `BlockHeader` with the incorrect block body hash.
34        block: Box<Block>,
35        /// The actual hash of the block's `BlockBody`.
36        actual_block_body_hash: Digest,
37    },
38
39    /// The block's hash is not the same as the header's hash.
40    #[error(
41        "block has incorrect block hash. \
42         actual block body hash: {actual_block_header_hash}, \
43         block: {block}"
44    )]
45    BlockHashMismatch {
46        /// The `Block` with the incorrect `BlockHeaderHash`
47        block: Box<Block>,
48        /// The actual hash of the block's `BlockHeader`
49        actual_block_header_hash: BlockHash,
50    },
51
52    /// Serialized value not contained in proof.
53    #[error("serialized value not contained in proof")]
54    SerializedValueNotContainedInProof,
55
56    /// No block in response.
57    #[error("no block in response")]
58    NoBlockInResponse,
59
60    /// Block hash requested does not correspond to response.
61    #[error("block hash requested does not correspond to response")]
62    UnexpectedBlockHash,
63
64    /// Block height was not as requested.
65    #[error("block height was not as requested")]
66    UnexpectedBlockHeight,
67
68    /// An invalid combination of state identifier and block header response
69    #[error("invalid combination of state identifier and block header in response")]
70    InvalidGlobalStateResponse,
71}
72
73impl From<bytesrepr::Error> for ValidateResponseError {
74    fn from(e: bytesrepr::Error) -> Self {
75        ValidateResponseError::BytesRepr(e)
76    }
77}
78
79pub(crate) fn validate_get_block_result(
80    maybe_block_identifier: Option<BlockIdentifier>,
81    result: &GetBlockResult,
82) -> Result<(), ValidateResponseError> {
83    let block = if let Some(block) = result.block.as_ref() {
84        block
85    } else {
86        return Ok(());
87    };
88
89    match types::validate_block_hashes_v1(block) {
90        Ok(()) => {}
91        Err(v1_error) => match types::validate_block_hashes_v2(block) {
92            Ok(()) => {}
93            Err(_v2_error) => return Err(v1_error),
94        },
95    }
96
97    match maybe_block_identifier {
98        Some(BlockIdentifier::Hash(block_hash)) => {
99            if block_hash.inner() != block.hash().inner() {
100                return Err(ValidateResponseError::UnexpectedBlockHash);
101            }
102        }
103        Some(BlockIdentifier::Height(height)) => {
104            // More is necessary here to mitigate a MITM attack
105            if height != block.header().height() {
106                return Err(ValidateResponseError::UnexpectedBlockHeight);
107            }
108        }
109        // More is necessary here to mitigate a MITM attack. In this case we would want to validate
110        // `block.proofs()` to make sure that 1/3 of the validator weight signed the block, and we
111        // would have to know the latest validators through some trustworthy means
112        None => (),
113    }
114    Ok(())
115}