use std::convert::TryFrom;
use jsonrpc_lite::JsonRpc;
use thiserror::Error;
use casper_execution_engine::{
core, core::ValidationError, storage::trie::merkle_proof::TrieMerkleProof,
};
use casper_hashing::Digest;
use casper_node::{
rpcs::{
chain::{BlockIdentifier, EraSummary, GetEraInfoResult},
state::GlobalStateIdentifier,
},
types::{
json_compatibility, Block, BlockHeader, BlockValidationError, JsonBlock, JsonBlockHeader,
},
};
use casper_types::{bytesrepr, Key, StoredValue, U512};
const GET_ITEM_RESULT_BALANCE_VALUE: &str = "balance_value";
const GET_ITEM_RESULT_STORED_VALUE: &str = "stored_value";
const GET_ITEM_RESULT_MERKLE_PROOF: &str = "merkle_proof";
const QUERY_GLOBAL_STATE_BLOCK_HEADER: &str = "block_header";
#[derive(Error, Debug)]
pub enum ValidateResponseError {
#[error("Failed to marshall value {0}")]
BytesRepr(bytesrepr::Error),
#[error(transparent)]
Serde(#[from] serde_json::Error),
#[error("validate_response failed to parse")]
ValidateResponseFailedToParse,
#[error(transparent)]
ValidationError(#[from] ValidationError),
#[error("Block validation error {0}")]
BlockValidationError(BlockValidationError),
#[error("serialized value not contained in proof")]
SerializedValueNotContainedInProof,
#[error("no block in response")]
NoBlockInResponse,
#[error("block hash requested does not correspond to response")]
UnexpectedBlockHash,
#[error("block height was not as requested")]
UnexpectedBlockHeight,
#[error("Invalid combination of state identifier and block header in response")]
InvalidGlobalStateResponse,
}
impl From<bytesrepr::Error> for ValidateResponseError {
fn from(e: bytesrepr::Error) -> Self {
ValidateResponseError::BytesRepr(e)
}
}
impl From<BlockValidationError> for ValidateResponseError {
fn from(e: BlockValidationError) -> Self {
ValidateResponseError::BlockValidationError(e)
}
}
pub(crate) fn validate_get_era_info_response(
response: &JsonRpc,
) -> Result<(), ValidateResponseError> {
let value = response
.get_result()
.ok_or(ValidateResponseError::ValidateResponseFailedToParse)?;
let result: GetEraInfoResult = serde_json::from_value(value.to_owned())?;
match result.era_summary {
Some(EraSummary {
state_root_hash,
era_id,
merkle_proof,
stored_value,
..
}) => {
let proof_bytes = base16::decode(&merkle_proof)
.map_err(|_| ValidateResponseError::ValidateResponseFailedToParse)?;
let proofs: Vec<TrieMerkleProof<Key, StoredValue>> =
bytesrepr::deserialize(proof_bytes)?;
let key = Key::EraInfo(era_id);
let path = &[];
let proof_value = match stored_value {
json_compatibility::StoredValue::EraInfo(era_info) => {
StoredValue::EraInfo(era_info)
}
_ => return Err(ValidateResponseError::ValidateResponseFailedToParse),
};
core::validate_query_proof(&state_root_hash, &proofs, &key, path, &proof_value)
.map_err(Into::into)
}
None => Ok(()),
}
}
pub(crate) fn validate_query_response(
response: &JsonRpc,
state_root_hash: &Digest,
key: &Key,
path: &[String],
) -> Result<(), ValidateResponseError> {
let value = response
.get_result()
.ok_or(ValidateResponseError::ValidateResponseFailedToParse)?;
let object = value
.as_object()
.ok_or(ValidateResponseError::ValidateResponseFailedToParse)?;
let proofs: Vec<TrieMerkleProof<Key, StoredValue>> = {
let proof = object
.get(GET_ITEM_RESULT_MERKLE_PROOF)
.ok_or(ValidateResponseError::ValidateResponseFailedToParse)?;
let proof_str = proof
.as_str()
.ok_or(ValidateResponseError::ValidateResponseFailedToParse)?;
let proof_bytes = base16::decode(proof_str)
.map_err(|_| ValidateResponseError::ValidateResponseFailedToParse)?;
bytesrepr::deserialize(proof_bytes)?
};
let proof_value: &StoredValue = {
let last_proof = proofs
.last()
.ok_or(ValidateResponseError::ValidateResponseFailedToParse)?;
last_proof.value()
};
{
let value: json_compatibility::StoredValue = {
let value = object
.get(GET_ITEM_RESULT_STORED_VALUE)
.ok_or(ValidateResponseError::ValidateResponseFailedToParse)?;
serde_json::from_value(value.to_owned())?
};
match json_compatibility::StoredValue::try_from(proof_value.clone()) {
Ok(json_proof_value) if json_proof_value == value => (),
_ => return Err(ValidateResponseError::SerializedValueNotContainedInProof),
}
}
core::validate_query_proof(state_root_hash, &proofs, key, path, proof_value).map_err(Into::into)
}
pub(crate) fn validate_query_global_state(
response: &JsonRpc,
state_identifier: GlobalStateIdentifier,
key: &Key,
path: &[String],
) -> Result<(), ValidateResponseError> {
let value = response
.get_result()
.ok_or(ValidateResponseError::ValidateResponseFailedToParse)?;
let object = value
.as_object()
.ok_or(ValidateResponseError::ValidateResponseFailedToParse)?;
let proofs: Vec<TrieMerkleProof<Key, StoredValue>> = {
let proof = object
.get(GET_ITEM_RESULT_MERKLE_PROOF)
.ok_or(ValidateResponseError::ValidateResponseFailedToParse)?;
let proof_str = proof
.as_str()
.ok_or(ValidateResponseError::ValidateResponseFailedToParse)?;
let proof_bytes = base16::decode(proof_str)
.map_err(|_| ValidateResponseError::ValidateResponseFailedToParse)?;
bytesrepr::deserialize(proof_bytes)?
};
let proof_value: &StoredValue = {
let last_proof = proofs
.last()
.ok_or(ValidateResponseError::ValidateResponseFailedToParse)?;
last_proof.value()
};
let json_block_header_value = object
.get(QUERY_GLOBAL_STATE_BLOCK_HEADER)
.ok_or(ValidateResponseError::ValidateResponseFailedToParse)?;
let maybe_json_block_header: Option<JsonBlockHeader> =
serde_json::from_value(json_block_header_value.to_owned())?;
let state_root_hash = match (state_identifier, maybe_json_block_header) {
(GlobalStateIdentifier::BlockHash(_), None)
| (GlobalStateIdentifier::StateRootHash(_), Some(_)) => {
return Err(ValidateResponseError::InvalidGlobalStateResponse);
}
(GlobalStateIdentifier::BlockHash(_), Some(json_header)) => {
let block_header = BlockHeader::from(json_header);
*block_header.state_root_hash()
}
(GlobalStateIdentifier::StateRootHash(hash), None) => hash,
};
core::validate_query_proof(&state_root_hash, &proofs, key, path, proof_value)
.map_err(Into::into)
}
pub(crate) fn validate_get_balance_response(
response: &JsonRpc,
state_root_hash: &Digest,
key: &Key,
) -> Result<(), ValidateResponseError> {
let value = response
.get_result()
.ok_or(ValidateResponseError::ValidateResponseFailedToParse)?;
let object = value
.as_object()
.ok_or(ValidateResponseError::ValidateResponseFailedToParse)?;
let balance_proof: TrieMerkleProof<Key, StoredValue> = {
let proof = object
.get(GET_ITEM_RESULT_MERKLE_PROOF)
.ok_or(ValidateResponseError::ValidateResponseFailedToParse)?;
let proof_str = proof
.as_str()
.ok_or(ValidateResponseError::ValidateResponseFailedToParse)?;
let proof_bytes = base16::decode(proof_str)
.map_err(|_| ValidateResponseError::ValidateResponseFailedToParse)?;
bytesrepr::deserialize(proof_bytes)?
};
let balance: U512 = {
let value = object
.get(GET_ITEM_RESULT_BALANCE_VALUE)
.ok_or(ValidateResponseError::ValidateResponseFailedToParse)?;
let value_str = value
.as_str()
.ok_or(ValidateResponseError::ValidateResponseFailedToParse)?;
U512::from_dec_str(value_str)
.map_err(|_| ValidateResponseError::ValidateResponseFailedToParse)?
};
core::validate_balance_proof(state_root_hash, &balance_proof, *key, &balance)
.map_err(Into::into)
}
pub(crate) fn validate_get_block_response(
response: &JsonRpc,
maybe_block_identifier: &Option<BlockIdentifier>,
) -> Result<(), ValidateResponseError> {
let maybe_result = response.get_result();
let json_block_value = maybe_result
.and_then(|value| value.get("block"))
.ok_or(ValidateResponseError::NoBlockInResponse)?;
let maybe_json_block: Option<JsonBlock> = serde_json::from_value(json_block_value.to_owned())?;
let json_block = if let Some(json_block) = maybe_json_block {
json_block
} else {
return Ok(());
};
let block = Block::from(json_block);
block.verify()?;
match maybe_block_identifier {
Some(BlockIdentifier::Hash(block_hash)) => {
if block_hash != block.hash() {
return Err(ValidateResponseError::UnexpectedBlockHash);
}
}
Some(BlockIdentifier::Height(height)) => {
if height != &block.height() {
return Err(ValidateResponseError::UnexpectedBlockHeight);
}
}
None => (),
}
Ok(())
}