Skip to content

Commit

Permalink
Merge branch 'shuo/hash_of_nested_map' into 'master'
Browse files Browse the repository at this point in the history
feat: hash_of_map supports nested Map

This MR extend the `hash_of_map` function to support compute the hash of nested Map.

1. extend `Enum RawHttpRequestVal` with new variant Map
2. implement the hash_val method for Map
3. hashing only takes references. This avoids unnecessary clone of values at each level of the nested Map
4. use this new way to compute the response hash
5. add unit tests to guard hash_of_map from potential future accidental changes
6. update spec compliance test for the new functionality and response hash 

See merge request dfinity-lab/public/ic!15585
  • Loading branch information
ShuoWangNSL committed Oct 25, 2023
2 parents ed544c9 + 9d938da commit 156f472
Show file tree
Hide file tree
Showing 5 changed files with 135 additions and 49 deletions.
14 changes: 7 additions & 7 deletions hs/spec_compliance/src/IC/HTTP/RequestId.hs
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@ requestId (GRec hm) = sha256 $ BS.concat $ sort $ map encodeKV $ HM.toList hm
requestId _ = error "requestID: expected a record"

encodeKV :: (T.Text, GenR) -> BS.ByteString
encodeKV (k, v) = sha256 (toUtf8 k) <> sha256 (encodeVal v)
encodeKV (k, v) = sha256 (toUtf8 k) <> hashVal v

encodeVal :: GenR -> BS.ByteString
encodeVal (GBlob b) = b
encodeVal (GText t) = toUtf8 t
encodeVal (GNat n) = encodeNat n
encodeVal (GRec _) = error "requestID: Nested record"
encodeVal (GList vs) = BS.concat $ map (sha256 . encodeVal) vs
hashVal :: GenR -> BS.ByteString
hashVal (GBlob b) = sha256 b
hashVal (GText t) = sha256 (toUtf8 t)
hashVal (GNat n) = sha256 (encodeNat n)
hashVal val@(GRec _) = requestId val
hashVal (GList vs) = sha256 $ BS.concat $ map hashVal vs

encodeNat :: Natural -> BS.ByteString
encodeNat = BS.fromStrict . toLEB128
2 changes: 1 addition & 1 deletion hs/spec_compliance/src/IC/Test/Agent.hs
Original file line number Diff line number Diff line change
Expand Up @@ -864,7 +864,7 @@ checkQueryResponse cid rid r = do
requestId $
rec
[ "status" =: GText "replied",
"reply" =: GBlob (requestId $ rec ["arg" =: GBlob payload]),
"reply" =: rec ["arg" =: GBlob payload],
"timestamp" =: GNat t,
"request_id" =: GBlob rid
]
Expand Down
27 changes: 16 additions & 11 deletions rs/types/types/src/messages/http.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,12 @@ use maplit::btreemap;
#[cfg(test)]
use proptest_derive::Arbitrary;
use serde::{ser::SerializeTuple, Deserialize, Serialize};
use std::{collections::BTreeSet, convert::TryFrom, error::Error, fmt};
use std::{
collections::{BTreeMap, BTreeSet},
convert::TryFrom,
error::Error,
fmt,
};

#[cfg(test)]
mod tests;
Expand Down Expand Up @@ -558,6 +563,7 @@ pub enum RawHttpRequestVal {
String(String),
U64(u64),
Array(Vec<RawHttpRequestVal>),
Map(BTreeMap<String, RawHttpRequestVal>),
}

/// The reply to an update call.
Expand Down Expand Up @@ -594,11 +600,15 @@ impl QueryResponseHash {

let self_map_representation = match response {
HttpQueryResponse::Replied { reply } => {
let map_of_reply = btreemap! {
"arg".to_string() => RawHttpRequestVal::Bytes(reply.arg.0.clone()),
};

btreemap! {
"request_id".to_string() => Bytes(request.id().as_bytes().to_vec()),
"status".to_string() => String("replied".to_string()),
"timestamp".to_string() => U64(timestamp.as_nanos_since_unix_epoch()),
"reply".to_string() => Bytes(reply.representation_independent_hash().to_vec()),
"reply".to_string() => Map(map_of_reply)
}
}
HttpQueryResponse::Rejected {
Expand All @@ -622,6 +632,10 @@ impl QueryResponseHash {

Self(hash)
}

pub fn as_bytes(&self) -> &[u8; 32] {
&self.0
}
}

impl SignedBytesWithoutDomainSeparator for QueryResponseHash {
Expand Down Expand Up @@ -678,15 +692,6 @@ pub struct HttpQueryResponseReply {
pub arg: Blob,
}

impl HttpQueryResponseReply {
/// Returns the representation-independent hash.
pub fn representation_independent_hash(&self) -> [u8; 32] {
hash_of_map(&btreemap! {
"arg".to_string() => RawHttpRequestVal::Bytes(self.arg.0.clone()),
})
}
}

/// The response to a `read_state` request.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct HttpReadStateResponse {
Expand Down
53 changes: 51 additions & 2 deletions rs/types/types/src/messages/http/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,7 @@ mod try_from {
}
}

mod query {
pub(super) mod query {
use super::super::to_blob;
use super::*;
use crate::messages::http::{
Expand All @@ -354,7 +354,7 @@ mod try_from {
}
}

fn default_user_query_content() -> UserQuery {
pub fn default_user_query_content() -> UserQuery {
UserQuery {
source: UserId::from(fixed::principal_id()),
receiver: fixed::canister_id(),
Expand Down Expand Up @@ -551,6 +551,55 @@ mod try_from {
pub use fixed_test_values as fixed;
}

mod hashing {
use super::try_from::query;
use crate::{
messages::{Blob, HttpQueryResponse, HttpQueryResponseReply, QueryResponseHash},
Time,
};
use hex_literal::hex;

#[test]
fn hashing_query_response_reply() {
let time = 2614;
let query_response = HttpQueryResponse::Replied {
reply: HttpQueryResponseReply {
arg: Blob(b"some_bytes".to_vec()),
},
};
let user_query = query::default_user_query_content();
let query_response_hash = QueryResponseHash::new(
&query_response,
&user_query,
Time::from_nanos_since_unix_epoch(time),
);
assert_eq!(
query_response_hash.as_bytes(),
&hex!("7e94e73d1647506682a6300385bc99a63d1ef655222e5a3235f784ca3e80dca4")
);
}

#[test]
fn hashing_query_response_reject() {
let time = 2614;
let query_response = HttpQueryResponse::Rejected {
reject_code: 1,
reject_message: "system error".to_string(),
error_code: "IC500".to_string(),
};
let user_query = query::default_user_query_content();
let query_response_hash = QueryResponseHash::new(
&query_response,
&user_query,
Time::from_nanos_since_unix_epoch(time),
);
assert_eq!(
query_response_hash.as_bytes(),
&hex!("bd80f930dfd3eafdf2d5c03031da9b5ec62963701abcb217d31c84005bd8db87")
);
}
}

mod cbor_serialization {
use crate::{
messages::{
Expand Down
Loading

0 comments on commit 156f472

Please sign in to comment.