diff --git a/Cargo.lock b/Cargo.lock index ffcd5efbd..707de916d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2588,6 +2588,7 @@ dependencies = [ "pocket-ic", "rand", "serde", + "serde_bytes", "serde_cbor", "sha2 0.10.9", "tokio", diff --git a/ic-agent/src/agent/agent_test.rs b/ic-agent/src/agent/agent_test.rs index bcbc7bb89..b260cd783 100644 --- a/ic-agent/src/agent/agent_test.rs +++ b/ic-agent/src/agent/agent_test.rs @@ -61,7 +61,7 @@ async fn query() -> Result<(), AgentError> { let (query_mock, url) = mock( "POST", - "/api/v2/canister/aaaaa-aa/query", + "/api/v3/canister/aaaaa-aa/query", 200, serde_cbor::to_vec(&response)?, Some("application/cbor"), @@ -92,7 +92,7 @@ async fn query() -> Result<(), AgentError> { #[cfg_attr(target_family = "wasm", wasm_bindgen_test)] async fn query_error() -> Result<(), AgentError> { let (query_mock, url) = - mock("POST", "/api/v2/canister/aaaaa-aa/query", 500, vec![], None).await; + mock("POST", "/api/v3/canister/aaaaa-aa/query", 500, vec![], None).await; let agent = make_agent(&url); let result = agent @@ -128,7 +128,7 @@ async fn query_rejected() -> Result<(), AgentError> { let (query_mock, url) = mock( "POST", - "/api/v2/canister/aaaaa-aa/query", + "/api/v3/canister/aaaaa-aa/query", 200, serde_cbor::to_vec(&response)?, Some("application/cbor"), @@ -169,7 +169,7 @@ async fn query_rejected() -> Result<(), AgentError> { #[cfg_attr(not(target_family = "wasm"), tokio::test)] #[cfg_attr(target_family = "wasm", wasm_bindgen_test)] async fn call_error() -> Result<(), AgentError> { - let (call_mock, url) = mock("POST", "/api/v3/canister/aaaaa-aa/call", 500, vec![], None).await; + let (call_mock, url) = mock("POST", "/api/v4/canister/aaaaa-aa/call", 500, vec![], None).await; let agent = make_agent(&url); @@ -201,7 +201,7 @@ async fn call_rejected() -> Result<(), AgentError> { let (call_mock, url) = mock( "POST", - "/api/v3/canister/aaaaa-aa/call", + "/api/v4/canister/aaaaa-aa/call", 200, body, Some("application/cbor"), @@ -242,7 +242,7 @@ async fn call_rejected_without_error_code() -> Result<(), AgentError> { let (call_mock, url) = mock( "POST", - format!("/api/v3/canister/{canister_id_str}/call").as_str(), + format!("/api/v4/canister/{canister_id_str}/call").as_str(), 200, body, Some("application/cbor"), @@ -364,20 +364,14 @@ async fn status_error() -> Result<(), AgentError> { Ok(()) } -// these values for canister, paths, and mock_response are captured from a real request to mainnet -// the response amounts to "method not found" -// we don't really care about the response since we're just testing the cert verification -const REQ_WITH_DELEGATED_CERT_PATH: [&str; 2] = [ - "726571756573745F737461747573", - "92F03ABDDC774EE97882320CF15F2029A868FFCFE3BE48FEF84FC97B5A13E04A", -]; +const REQ_WITH_DELEGATED_CERT_PATH: [&str; 1] = ["time"]; const REQ_WITH_DELEGATED_CERT_CANISTER: &str = "ivg37-qiaaa-aaaab-aaaga-cai"; -const REQ_WITH_DELEGATED_CERT_RESPONSE: &[u8] = - include_bytes!("agent_test/req_with_delegated_cert_response.bin"); +// This file is a mainnet POST response body to /api/v3/canister/ivg37-.../read_state with path /time +const REQ_WITH_DELEGATED_CERT_RESPONSE: &[u8] = include_bytes!("agent_test/ivg37_time.bin"); -// this is the same response as REQ_WITH_DELEGATED_CERT_RESPONSE, but with a manually pruned -// /subnet//canister_ranges field -const PRUNED_SUBNET: &[u8] = include_bytes!("agent_test/pruned_subnet.bin"); +// this is the same response as REQ_WITH_DELEGATED_CERT_RESPONSE, but with a manually pruned /canister_ranges. +// Run the ref-tests bin prune-ranges to generate it. +const PRUNED_RANGES: &[u8] = include_bytes!("agent_test/ivg37_time_pruned_ranges.bin"); #[cfg_attr(not(target_family = "wasm"), tokio::test)] #[cfg_attr(target_family = "wasm", wasm_bindgen_test)] @@ -386,7 +380,7 @@ const PRUNED_SUBNET: &[u8] = include_bytes!("agent_test/pruned_subnet.bin"); async fn check_subnet_range_with_valid_range() { let (_read_mock, url) = mock( "POST", - "/api/v2/canister/ivg37-qiaaa-aaaab-aaaga-cai/read_state", + "/api/v3/canister/ivg37-qiaaa-aaaab-aaaga-cai/read_state", 200, REQ_WITH_DELEGATED_CERT_RESPONSE.into(), Some("application/cbor"), @@ -415,7 +409,7 @@ async fn check_subnet_range_with_unauthorized_range() { let wrong_canister = Principal::from_text("ryjl3-tyaaa-aaaaa-aaaba-cai").unwrap(); let (_read_mock, url) = mock( "POST", - "/api/v2/canister/ryjl3-tyaaa-aaaaa-aaaba-cai/read_state", + "/api/v3/canister/ryjl3-tyaaa-aaaaa-aaaba-cai/read_state", 200, REQ_WITH_DELEGATED_CERT_RESPONSE.into(), Some("application/cbor"), @@ -443,9 +437,9 @@ async fn check_subnet_range_with_pruned_range() { let canister = Principal::from_text("ivg37-qiaaa-aaaab-aaaga-cai").unwrap(); let (_read_mock, url) = mock( "POST", - "/api/v2/canister/ivg37-qiaaa-aaaab-aaaga-cai/read_state", + "/api/v3/canister/ivg37-qiaaa-aaaab-aaaga-cai/read_state", 200, - PRUNED_SUBNET.into(), + PRUNED_RANGES.into(), Some("application/cbor"), ) .await; @@ -462,17 +456,25 @@ async fn check_subnet_range_with_pruned_range() { assert!(result.is_err()); } -const WRONG_SUBNET_CERT: &[u8] = include_bytes!("agent_test/wrong_subnet.bin"); - #[cfg_attr(not(target_family = "wasm"), tokio::test)] #[cfg_attr(target_family = "wasm", wasm_bindgen_test)] async fn wrong_subnet_query_certificate() { - let canister = Principal::from_text("224od-giaaa-aaaao-ae5vq-cai").unwrap(); + // these responses are for canister 224od-giaaa-aaaao-ae5vq-cai + let wrong_canister = Principal::from_text("rdmx6-jaaaa-aaaaa-aaadq-cai").unwrap(); let (mut read_mock, url) = mock( "POST", - "/api/v2/canister/224od-giaaa-aaaao-ae5vq-cai/read_state", + &format!("/api/v3/canister/{wrong_canister}/read_state"), 200, - WRONG_SUBNET_CERT.into(), + TIME_224OD.into(), + Some("application/cbor"), + ) + .await; + mock_additional( + &mut read_mock, + "POST", + "/api/v3/subnet/o3ow2-2ipam-6fcjo-3j5vt-fzbge-2g7my-5fz2m-p4o2t-dwlc4-gt2q7-5ae/read_state", + 200, + SUBNET_KEY_O3OW2.into(), Some("application/cbor"), ) .await; @@ -488,27 +490,30 @@ async fn wrong_subnet_query_certificate() { mock_additional( &mut read_mock, "POST", - "/api/v2/canister/224od-giaaa-aaaao-ae5vq-cai/query", + &format!("/api/v3/canister/{wrong_canister}/query"), 200, serde_cbor::to_vec(&response).unwrap(), Some("application/cbor"), ) .await; let agent = make_certifying_agent(&url); - let result = agent.query(&canister, "getVersion").call().await; + let result = agent.query(&wrong_canister, "getVersion").call().await; assert!(matches!( - result.unwrap_err(), + dbg!(result.unwrap_err()), AgentError::CertificateNotAuthorized() )); assert_single_mock( "POST", - "/api/v2/canister/224od-giaaa-aaaao-ae5vq-cai/read_state", + &format!("/api/v3/canister/{wrong_canister}/read_state"), &read_mock, ) .await; } -const GOOD_SUBNET_KEYS: &[u8] = include_bytes!("agent_test/subnet_keys.bin"); +// This file is a mainnet POST response body to /api/v3/subnet/o3ow2-.../read_state with path /subnet/hex(o3ow2-...)/public_key +const SUBNET_KEY_O3OW2: &[u8] = include_bytes!("agent_test/o3ow2_subnet_key.bin"); +// This file is a mainnet POST response body to /api/v3/canister/224od-.../read_state with path /time +const TIME_224OD: &[u8] = include_bytes!("agent_test/224od_time.bin"); #[cfg_attr(not(target_family = "wasm"), tokio::test)] #[cfg_attr(target_family = "wasm", wasm_bindgen_test)] @@ -516,9 +521,18 @@ async fn no_cert() { let canister = Principal::from_text("224od-giaaa-aaaao-ae5vq-cai").unwrap(); let (mut read_mock, url) = mock( "POST", - "/api/v2/canister/224od-giaaa-aaaao-ae5vq-cai/read_state", + "/api/v3/subnet/o3ow2-2ipam-6fcjo-3j5vt-fzbge-2g7my-5fz2m-p4o2t-dwlc4-gt2q7-5ae/read_state", 200, - GOOD_SUBNET_KEYS.into(), + SUBNET_KEY_O3OW2.into(), + Some("application/cbor"), + ) + .await; + mock_additional( + &mut read_mock, + "POST", + "/api/v3/canister/224od-giaaa-aaaao-ae5vq-cai/read_state", + 200, + TIME_224OD.into(), Some("application/cbor"), ) .await; @@ -530,7 +544,7 @@ async fn no_cert() { mock_additional( &mut read_mock, "POST", - "/api/v2/canister/224od-giaaa-aaaao-ae5vq-cai/query", + "/api/v3/canister/224od-giaaa-aaaao-ae5vq-cai/query", 200, serde_cbor::to_vec(&response).unwrap(), Some("application/cbor"), @@ -541,8 +555,8 @@ async fn no_cert() { assert!(matches!(result.unwrap_err(), AgentError::MissingSignature)); assert_mock(read_mock).await; } - -const RESP_WITH_SUBNET_KEY: &[u8] = include_bytes!("agent_test/with_subnet_key.bin"); +// This file is a mainnet POST response body to /api/v3/subnet/uzr34-.../read_state with paths /canister_ranges/hex(uzr34-...) and /subnet/hex(uzr34-...) +const NODE_KEYS_UZR34: &[u8] = include_bytes!("agent_test/uzr34_node_keys.bin"); #[cfg_attr(not(target_family = "wasm"), tokio::test)] #[cfg_attr(target_family = "wasm", wasm_bindgen_test)] @@ -563,31 +577,27 @@ async fn too_many_delegations() { current } - let canister_id_str = "rdmx6-jaaaa-aaaaa-aaadq-cai"; - let canister_id = Principal::from_text(canister_id_str).unwrap(); - let subnet_id = Vec::from( + let subnet_id = Principal::from_text("uzr34-akd3s-xrdag-3ql62-ocgoh-ld2ao-tamcv-54e7j-krwgb-2gm4z-oqe") - .unwrap() - .as_slice(), - ); + .unwrap(); let (_read_mock, url) = mock( "POST", - format!("/api/v2/canister/{canister_id_str}/read_state").as_str(), + format!("/api/v3/subnet/{subnet_id}/read_state").as_str(), 200, - RESP_WITH_SUBNET_KEY.into(), + NODE_KEYS_UZR34.into(), Some("application/cbor"), ) .await; let path_label = Label::from_bytes("subnet".as_bytes()); let agent = make_untimed_agent(&url); let cert = agent - .read_state_raw(vec![vec![path_label]], canister_id) + .read_subnet_state_raw(vec![vec![path_label]], subnet_id) .await .expect("read state failed"); - let new_cert = self_delegate_cert(&subnet_id, &cert, 1); + let new_cert = self_delegate_cert(subnet_id.as_slice(), &cert, 1); assert!(matches!( - agent.verify(&new_cert, canister_id).unwrap_err(), + agent.verify_for_subnet(&new_cert, subnet_id).unwrap_err(), AgentError::CertificateHasTooManyDelegations )); } @@ -597,7 +607,7 @@ async fn too_many_delegations() { async fn retry_ratelimit() { let (mut mock, url) = mock( "POST", - "/api/v2/canister/ryjl3-tyaaa-aaaaa-aaaba-cai/query", + "/api/v3/canister/ryjl3-tyaaa-aaaaa-aaaba-cai/query", 429, vec![], Some("text/plain"), @@ -610,7 +620,7 @@ async fn retry_ratelimit() { }; assert_single_mock_count( "POST", - "/api/v2/canister/ryjl3-tyaaa-aaaaa-aaaba-cai/query", + "/api/v3/canister/ryjl3-tyaaa-aaaaa-aaaba-cai/query", 2, &mut mock, ) diff --git a/ic-agent/src/agent/agent_test/224od_time.bin b/ic-agent/src/agent/agent_test/224od_time.bin new file mode 100644 index 000000000..67a53dd8e Binary files /dev/null and b/ic-agent/src/agent/agent_test/224od_time.bin differ diff --git a/ic-agent/src/agent/agent_test/ivg37_time.bin b/ic-agent/src/agent/agent_test/ivg37_time.bin new file mode 100644 index 000000000..01d5c7f5d Binary files /dev/null and b/ic-agent/src/agent/agent_test/ivg37_time.bin differ diff --git a/ic-agent/src/agent/agent_test/ivg37_time_pruned_ranges.bin b/ic-agent/src/agent/agent_test/ivg37_time_pruned_ranges.bin new file mode 100644 index 000000000..8074be13f Binary files /dev/null and b/ic-agent/src/agent/agent_test/ivg37_time_pruned_ranges.bin differ diff --git a/ic-agent/src/agent/agent_test/o3ow2_subnet_key.bin b/ic-agent/src/agent/agent_test/o3ow2_subnet_key.bin new file mode 100644 index 000000000..95ab22513 Binary files /dev/null and b/ic-agent/src/agent/agent_test/o3ow2_subnet_key.bin differ diff --git a/ic-agent/src/agent/agent_test/pruned_subnet.bin b/ic-agent/src/agent/agent_test/pruned_subnet.bin deleted file mode 100644 index d2c65ff5e..000000000 Binary files a/ic-agent/src/agent/agent_test/pruned_subnet.bin and /dev/null differ diff --git a/ic-agent/src/agent/agent_test/req_with_delegated_cert_response.bin b/ic-agent/src/agent/agent_test/req_with_delegated_cert_response.bin deleted file mode 100644 index e545ff68f..000000000 Binary files a/ic-agent/src/agent/agent_test/req_with_delegated_cert_response.bin and /dev/null differ diff --git a/ic-agent/src/agent/agent_test/subnet_keys.bin b/ic-agent/src/agent/agent_test/subnet_keys.bin deleted file mode 100644 index 61bf5b694..000000000 Binary files a/ic-agent/src/agent/agent_test/subnet_keys.bin and /dev/null differ diff --git a/ic-agent/src/agent/agent_test/uzr34_node_keys.bin b/ic-agent/src/agent/agent_test/uzr34_node_keys.bin new file mode 100644 index 000000000..b396c0d74 Binary files /dev/null and b/ic-agent/src/agent/agent_test/uzr34_node_keys.bin differ diff --git a/ic-agent/src/agent/agent_test/with_subnet_key.bin b/ic-agent/src/agent/agent_test/with_subnet_key.bin deleted file mode 100644 index 99e9889ec..000000000 Binary files a/ic-agent/src/agent/agent_test/with_subnet_key.bin and /dev/null differ diff --git a/ic-agent/src/agent/agent_test/wrong_subnet.bin b/ic-agent/src/agent/agent_test/wrong_subnet.bin deleted file mode 100644 index a16c26e93..000000000 Binary files a/ic-agent/src/agent/agent_test/wrong_subnet.bin and /dev/null differ diff --git a/ic-agent/src/agent/mod.rs b/ic-agent/src/agent/mod.rs index e8b3bcb81..e1751dca6 100644 --- a/ic-agent/src/agent/mod.rs +++ b/ic-agent/src/agent/mod.rs @@ -46,7 +46,7 @@ use crate::{ agent::response_authentication::{ extract_der, lookup_canister_info, lookup_canister_metadata, lookup_request_status, lookup_subnet, lookup_subnet_canister_ranges, lookup_subnet_metrics, lookup_time, - lookup_value, + lookup_tree, lookup_value, }, agent_error::TransportError, export::Principal, @@ -322,7 +322,7 @@ impl Agent { let bytes = self .execute( Method::POST, - &format!("api/v2/canister/{}/query", effective_canister_id.to_text()), + &format!("api/v3/canister/{}/query", effective_canister_id.to_text()), Some(serialized_bytes), ) .await? @@ -340,7 +340,7 @@ impl Agent { { let _permit = self.concurrent_requests_semaphore.acquire().await; let endpoint = format!( - "api/v2/canister/{}/read_state", + "api/v3/canister/{}/read_state", effective_canister_id.to_text() ); let bytes = self @@ -359,7 +359,7 @@ impl Agent { A: serde::de::DeserializeOwned, { let _permit = self.concurrent_requests_semaphore.acquire().await; - let endpoint = format!("api/v2/subnet/{}/read_state", subnet_id.to_text()); + let endpoint = format!("api/v3/subnet/{}/read_state", subnet_id.to_text()); let bytes = self .execute(Method::POST, &endpoint, Some(serialized_bytes)) .await? @@ -373,7 +373,7 @@ impl Agent { serialized_bytes: Vec, ) -> Result { let _permit = self.concurrent_requests_semaphore.acquire().await; - let endpoint = format!("api/v3/canister/{}/call", effective_canister_id.to_text()); + let endpoint = format!("api/v4/canister/{}/call", effective_canister_id.to_text()); let (status_code, response_body) = self .execute(Method::POST, &endpoint, Some(serialized_bytes)) .await?; @@ -594,6 +594,7 @@ impl Agent { match response_body { TransportCallResponse::Replied { certificate } => { + dbg!(hex::encode(&certificate)); let certificate = serde_cbor::from_slice(&certificate).map_err(AgentError::InvalidCborData)?; @@ -973,12 +974,30 @@ impl Agent { return Err(AgentError::CertificateHasTooManyDelegations); } self.verify_cert(&cert, effective_canister_id)?; - let canister_range_lookup = [ - "subnet".as_bytes(), - delegation.subnet_id.as_ref(), - "canister_ranges".as_bytes(), - ]; - let canister_range = lookup_value(&cert.tree, canister_range_lookup)?; + let canister_range_shards_lookup = + ["canister_ranges".as_bytes(), delegation.subnet_id.as_ref()]; + let canister_range_shards = lookup_tree(&cert.tree, canister_range_shards_lookup)?; + let mut shard_paths = canister_range_shards + .list_paths() // /canister_ranges// + .into_iter() + .map(|mut x| { + x.pop() // flatten [label] to label + .ok_or_else(AgentError::CertificateVerificationFailed) + }) + .collect::, _>>()?; + if shard_paths.is_empty() { + return Err(AgentError::CertificateNotAuthorized()); + } + shard_paths.sort_unstable(); + let shard_division = shard_paths + .partition_point(|shard| shard.as_bytes() <= effective_canister_id.as_slice()); + if shard_division == 0 { + // the certificate is not authorized to answer calls for this canister + return Err(AgentError::CertificateNotAuthorized()); + } + let max_potential_shard = &shard_paths[shard_division - 1]; + let canister_range_lookup = [max_potential_shard.as_bytes()]; + let canister_range = lookup_value(&canister_range_shards, canister_range_lookup)?; let ranges: Vec<(Principal, Principal)> = serde_cbor::from_slice(canister_range).map_err(AgentError::InvalidCborData)?; if !principal_is_within_ranges(&effective_canister_id, &ranges[..]) { @@ -1209,7 +1228,7 @@ impl Agent { } } - /// Retrieve all existing API boundary nodes from the state tree via endpoint `/api/v2/canister//read_state` + /// Retrieve all existing API boundary nodes from the state tree via endpoint `/api/v3/canister//read_state` pub async fn fetch_api_boundary_nodes_by_canister_id( &self, canister_id: Principal, @@ -1220,7 +1239,7 @@ impl Agent { Ok(api_boundary_nodes) } - /// Retrieve all existing API boundary nodes from the state tree via endpoint `/api/v2/subnet//read_state` + /// Retrieve all existing API boundary nodes from the state tree via endpoint `/api/v3/subnet//read_state` pub async fn fetch_api_boundary_nodes_by_subnet_id( &self, subnet_id: Principal, @@ -1235,11 +1254,29 @@ impl Agent { &self, canister: &Principal, ) -> Result, AgentError> { - let cert = self - .read_state_raw(vec![vec!["subnet".into()]], *canister) + let canister_cert = self + .read_state_raw(vec![vec!["time".into()]], *canister) .await?; - - let (subnet_id, subnet) = lookup_subnet(&cert, &self.root_key.read().unwrap())?; + let subnet_id = if let Some(delegation) = canister_cert.delegation.as_ref() { + Principal::from_slice(&delegation.subnet_id) + } else { + Principal::self_authenticating(&self.root_key.read().unwrap()[..]) + }; + let subnet_cert = self + .read_subnet_state_raw( + vec![ + vec!["canister_ranges".into(), subnet_id.as_slice().into()], + vec!["subnet".into(), subnet_id.as_slice().into(), "node".into()], + vec![ + "subnet".into(), + subnet_id.as_slice().into(), + "public_key".into(), + ], + ], + subnet_id, + ) + .await?; + let subnet = lookup_subnet(&subnet_id, &subnet_cert)?; if !subnet.canister_ranges.contains(canister) { return Err(AgentError::CertificateNotAuthorized()); } diff --git a/ic-agent/src/agent/response_authentication.rs b/ic-agent/src/agent/response_authentication.rs index 141f4b2db..c92e2d335 100644 --- a/ic-agent/src/agent/response_authentication.rs +++ b/ic-agent/src/agent/response_authentication.rs @@ -185,28 +185,26 @@ pub(crate) fn lookup_reply>( Ok(RequestStatusResponse::Replied(ReplyResponse { arg })) } +/// The cert should contain both /subnet/ and /canister_ranges/ pub(crate) fn lookup_subnet + Clone>( + subnet_id: &Principal, certificate: &Certificate, - root_key: &[u8], -) -> Result<(Principal, Subnet), AgentError> { - let subnet_id = if let Some(delegation) = &certificate.delegation { - Principal::from_slice(delegation.subnet_id.as_ref()) - } else { - Principal::self_authenticating(root_key) - }; +) -> Result { let subnet_tree = lookup_tree(&certificate.tree, [b"subnet", subnet_id.as_slice()])?; let key = lookup_value(&subnet_tree, [b"public_key".as_ref()])?.to_vec(); - let canister_ranges: Vec<(Principal, Principal)> = - if let Some(delegation) = &certificate.delegation { - let delegation: Certificate> = - serde_cbor::from_slice(delegation.certificate.as_ref())?; - serde_cbor::from_slice(lookup_value( - &delegation.tree, - [b"subnet", subnet_id.as_slice(), b"canister_ranges"], - )?)? - } else { - serde_cbor::from_slice(lookup_value(&subnet_tree, [b"canister_ranges".as_ref()])?)? - }; + let canister_ranges_tree = lookup_tree( + &certificate.tree, + [b"canister_ranges", subnet_id.as_slice()], + )?; + let canister_ranges: Vec<(Principal, Principal)> = canister_ranges_tree + .list_paths() + .into_iter() + .try_fold(vec![], |mut ranges, shard| { + ranges.extend(serde_cbor::from_slice::>( + lookup_value(&canister_ranges_tree, [shard[0].as_bytes()])?, + )?); + Ok::<_, AgentError>(ranges) + })?; let node_keys_subtree = lookup_tree(&subnet_tree, [b"node".as_ref()])?; let mut node_keys = HashMap::new(); for path in node_keys_subtree.list_paths() { @@ -237,7 +235,7 @@ pub(crate) fn lookup_subnet + Clone>( _key: key, node_keys, }; - Ok((subnet_id, subnet)) + Ok(subnet) } pub(crate) fn lookup_api_boundary_nodes + Clone>( diff --git a/ic-transport-types/src/lib.rs b/ic-transport-types/src/lib.rs index 43e59f973..bfac03db5 100644 --- a/ic-transport-types/src/lib.rs +++ b/ic-transport-types/src/lib.rs @@ -327,7 +327,7 @@ impl TryFrom for RejectCode { #[error("Invalid reject code {0}")] pub struct InvalidRejectCodeError(pub u64); -/// The response of `/api/v2/canister//read_state` with `request_status` request type. +/// The response of `/api/v3/canister//read_state` with `request_status` request type. /// /// See [the HTTP interface specification](https://internetcomputer.org/docs/current/references/ic-interface-spec#http-call-overview) for more details. #[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Clone)] diff --git a/ref-tests/Cargo.toml b/ref-tests/Cargo.toml index 1079acc91..cdb60f181 100644 --- a/ref-tests/Cargo.toml +++ b/ref-tests/Cargo.toml @@ -8,6 +8,7 @@ publish = false [dependencies] candid = { workspace = true } +ic-certification = { workspace = true } ic-ed25519 = { workspace = true } ic-agent = { path = "../ic-agent" } ic-identity-hsm = { path = "../ic-identity-hsm" } @@ -17,10 +18,10 @@ p256 = { workspace = true } pocket-ic = { workspace = true } rand = { workspace = true } serde = { workspace = true, features = ["derive"] } +serde_cbor = { workspace = true } +serde_bytes = { workspace = true } sha2 = { workspace = true } tokio = { workspace = true, features = ["full"] } [dev-dependencies] -serde_cbor = { workspace = true } -ic-certification = { workspace = true } ic-management-canister-types = { workspace = true } diff --git a/ref-tests/src/bin/prune-ranges.rs b/ref-tests/src/bin/prune-ranges.rs new file mode 100644 index 000000000..cae9110ec --- /dev/null +++ b/ref-tests/src/bin/prune-ranges.rs @@ -0,0 +1,81 @@ +use ic_certification::{Certificate, Delegation, HashTreeNode}; +use serde::{Deserialize, Serialize}; +use serde_cbor::Serializer; + +fn main() { + let repo_dir = std::fs::canonicalize(format!("{}/..", env!("CARGO_MANIFEST_DIR"))).unwrap(); + let response = + std::fs::read(repo_dir.join("ic-agent/src/agent/agent_test/ivg37_time.bin")).unwrap(); + let response: Response = serde_cbor::from_slice(&response).unwrap(); + let cert: Certificate = serde_cbor::from_slice(&response.certificate).unwrap(); + let delegation = cert.delegation.clone().unwrap(); + let delegation_cert: Certificate = serde_cbor::from_slice(&delegation.certificate).unwrap(); + let mut pruned_delegation_tree = delegation_cert.tree.clone().into(); + prune_ranges(&mut pruned_delegation_tree); + assert_eq!( + pruned_delegation_tree.digest(), + delegation_cert.tree.digest() + ); + let pruned_delegation_cert = PrunedDelegationCert { + tree: pruned_delegation_tree, + signature: delegation_cert.signature, + }; + let pruned_delegation_cert = tagged_serialize(&pruned_delegation_cert); + let pruned_cert = Certificate { + delegation: Some(Delegation { + certificate: pruned_delegation_cert, + ..delegation + }), + ..cert + }; + let pruned_cert = tagged_serialize(&pruned_cert); + let pruned_response = Response { + certificate: pruned_cert, + }; + let pruned_response = tagged_serialize(&pruned_response); + std::fs::write( + repo_dir.join("ic-agent/src/agent/agent_test/ivg37_time_pruned_ranges.bin"), + pruned_response, + ) + .unwrap(); +} + +#[derive(Serialize, Deserialize)] +struct Response { + #[serde(with = "serde_bytes")] + certificate: Vec, +} + +// annoyingly you cannot convert HashTreeNode directly to HashTree +#[derive(Serialize, Deserialize)] +struct PrunedDelegationCert { + tree: HashTreeNode, + #[serde(with = "serde_bytes")] + signature: Vec, +} + +fn tagged_serialize(value: &T) -> Vec { + let mut buf = Vec::new(); + let mut serializer = Serializer::new(&mut buf); + serializer.self_describe().unwrap(); + value.serialize(&mut serializer).unwrap(); + buf +} + +fn prune_ranges(tree: &mut HashTreeNode) { + match tree { + HashTreeNode::Fork(lr) => { + let (left, right) = lr.as_mut(); + prune_ranges(left); + prune_ranges(right); + } + HashTreeNode::Labeled(label, subtree) => { + if label.as_bytes() == b"canister_ranges" { + *tree = HashTreeNode::Pruned(tree.digest()); + } else { + prune_ranges(subtree); + } + } + _ => {} + } +} diff --git a/scripts/download_reftest_assets.sh b/scripts/download_reftest_assets.sh index ad0a0b437..385337111 100755 --- a/scripts/download_reftest_assets.sh +++ b/scripts/download_reftest_assets.sh @@ -3,7 +3,7 @@ set -e cd "$(dirname "$0")/.." ic_commit_for_pic=3c7938320bad1a690f617836d8b799f3998cf3ac -ic_commit_for_universal_canister=44e6a4830a03f05101a33d7baeb1f92c5c8093bc +ic_commit_for_universal_canister=3c7938320bad1a690f617836d8b799f3998cf3ac wallet_ver=20230530 case $(uname -s) in @@ -18,5 +18,6 @@ case $(uname -m) in esac curl -L --fail "https://download.dfinity.systems/ic/${ic_commit_for_pic}/binaries/${arch}-${os}/pocket-ic.gz" -o ref-tests/assets/pocket-ic.gz gunzip -f ref-tests/assets/pocket-ic.gz +chmod a+x ref-tests/assets/pocket-ic curl -L --fail "https://download.dfinity.systems/ic/${ic_commit_for_universal_canister}/canisters/universal_canister.wasm.gz" -o ref-tests/assets/universal-canister.wasm.gz curl -L --fail "https://github.com/dfinity/cycles-wallet/releases/download/${wallet_ver}/wallet.wasm" -o ref-tests/assets/cycles-wallet.wasm