Skip to content

Commit

Permalink
Merge pull request #8 from cspr-rad/query_merkle_proofs
Browse files Browse the repository at this point in the history
Introducing `process_query_proofs`
  • Loading branch information
xcthulhu authored Jun 11, 2024
2 parents d8593a0 + d31764b commit 9f27877
Show file tree
Hide file tree
Showing 4 changed files with 142 additions and 3 deletions.
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "casper-litmus"
version = "0.1.0"
version = "0.1.1"
edition = "2021"

[dependencies]
Expand Down Expand Up @@ -29,6 +29,7 @@ casper-hashing = "3.0.0"
casper-execution-engine = "7.0.1"
casper-types = { version = "4.0.1", features = ["gens"] }
casper-node = "1.5.6"
hex = "0.4.3"
once_cell = "1.19.0"
serde_json = "1.0.111"
test-strategy = "0.3.1"
Expand Down
96 changes: 94 additions & 2 deletions src/merkle_proof.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
// and https://github.com/casper-network/casper-node/blob/76ea7104cda02fcf1bd6edb686fd00b162dabde8/execution_engine/src/storage/trie/merkle_proof.rs
use core::mem::MaybeUninit;

use alloc::{boxed::Box, vec::Vec};
use alloc::{boxed::Box, string::String, vec::Vec};

use casper_types::{
bytesrepr::{self, Bytes, FromBytes, ToBytes, U8_SERIALIZED_LENGTH},
Key, StoredValue,
CLValueError, Key, StoredValue,
};
#[cfg(test)]
use proptest::prelude::*;
Expand Down Expand Up @@ -498,6 +498,98 @@ impl FromBytes for TrieMerkleProof {
}
}

#[derive(Debug, PartialEq, Eq)]
pub enum ValidationError {
PathLengthDifferentThanProofLessOne,
UnexpectedKey,
UnexpectedValue,
InvalidProofHash,
PathCold,
BytesRepr(bytesrepr::Error),
KeyIsNotAURef(Key),
ValueToCLValueConversion,
CLValueError(CLValueError),
}

impl From<CLValueError> for ValidationError {
fn from(err: CLValueError) -> Self {
ValidationError::CLValueError(err)
}
}

impl From<bytesrepr::Error> for ValidationError {
fn from(error: bytesrepr::Error) -> Self {
Self::BytesRepr(error)
}
}

pub struct QueryInfo<'a, 'b> {
state_root: Digest,
key: &'a Key,
stored_value: &'b StoredValue,
}

impl<'a, 'b> QueryInfo<'a, 'b> {
pub fn state_root(&self) -> &Digest {
&self.state_root
}

pub fn key(&self) -> &'a Key {
&self.key
}

pub fn stored_value(&self) -> &'b StoredValue {
&self.stored_value
}
}

pub fn process_query_proofs<'a>(
proofs: &'a [TrieMerkleProof],
path: &[String],
) -> Result<QueryInfo<'a, 'a>, ValidationError> {
if proofs.len() != path.len() + 1 {
return Err(ValidationError::PathLengthDifferentThanProofLessOne);
}

let mut proofs_iter = proofs.iter();

// length check above means we are safe to unwrap here
let first_proof = proofs_iter.next().unwrap();

let state_root = first_proof.compute_state_hash()?;

let mut proof_value = first_proof.value();

for (proof, path_component) in proofs_iter.zip(path.iter()) {
let named_keys = match proof_value {
StoredValue::Account(account) => account.named_keys(),
StoredValue::Contract(contract) => contract.named_keys(),
_ => return Err(ValidationError::PathCold),
};

let key = match named_keys.get(path_component) {
Some(key) => key,
None => return Err(ValidationError::PathCold),
};

if proof.key() != &key.normalize() {
return Err(ValidationError::UnexpectedKey);
}

if state_root != proof.compute_state_hash()? {
return Err(ValidationError::InvalidProofHash);
}

proof_value = proof.value();
}

Ok(QueryInfo {
state_root,
key: first_proof.key(),
stored_value: proof_value,
})
}

#[cfg(test)]
mod test {
extern crate std;
Expand Down
1 change: 1 addition & 0 deletions tests/assets/query_merkle_proofs.txt

Large diffs are not rendered by default.

45 changes: 45 additions & 0 deletions tests/integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ use casper_litmus::{
block_header::BlockHeader,
json_compatibility::{JsonBlock, JsonBlockHeader},
kernel::LightClientKernel,
merkle_proof::{process_query_proofs, TrieMerkleProof},
};
use casper_types::{
bytesrepr::{deserialize_from_slice, ToBytes},
EraId,
};

use once_cell::sync::Lazy;

static BLOCKS_MAP: Lazy<BTreeMap<u64, JsonBlock>> = Lazy::new(|| {
Expand Down Expand Up @@ -128,3 +130,46 @@ fn update_kernel_history() {
assert_eq!(kernel.latest_block_hash(), json_block.hash());
}
}

#[test]
fn query_proofs() {
let proofs_hex = include_str!("assets/query_merkle_proofs.txt");
let proofs_bytes = hex::decode(proofs_hex).expect("should decode with hex");
let proofs: Vec<TrieMerkleProof> = casper_types::bytesrepr::deserialize(proofs_bytes)
.expect("should deserialize with bytesrepr");
let query_info = process_query_proofs(&proofs, &[]).unwrap();
assert_eq!(
"9253bf8484bae2b6e4d5302c792c6a79f729b2cc2a9d87beb262d3266a424efa",
query_info.state_root().to_hex(),
"hex of state root not as expected"
);
if let casper_types::StoredValue::Account(account) = query_info.stored_value() {
assert_eq!(
"account-hash-c39d7a6202e5558ffbf327985c55a95f606db48115599a216987b73daf409076",
serde_json::to_value(&account.account_hash())
.expect("should convert to serde_json::Value")
.as_str()
.expect("should be a string"),
"account hash not as expected"
);
} else {
panic!(
"StoredValue variant not as expected (should be Account): {:?}",
query_info.stored_value()
);
}
if let casper_types::Key::Account(account_hash) = query_info.key() {
assert_eq!(
"account-hash-c39d7a6202e5558ffbf327985c55a95f606db48115599a216987b73daf409076",
serde_json::to_value(account_hash)
.expect("should convert to serde_json::Value")
.as_str()
.expect("should be a string")
);
} else {
panic!(
"Key variant not as expected (should be Account): {:?}",
query_info.key()
);
}
}

0 comments on commit 9f27877

Please sign in to comment.