Skip to content

Commit

Permalink
Merge branch 'main' into spofford/access-certificate
Browse files Browse the repository at this point in the history
  • Loading branch information
adamspofford-dfinity authored Oct 17, 2024
2 parents a61c7c6 + 653c953 commit 75a7270
Show file tree
Hide file tree
Showing 25 changed files with 296 additions and 342 deletions.
8 changes: 4 additions & 4 deletions ic-agent/src/agent/agent_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,15 +98,15 @@ pub enum AgentError {
#[error("The request status ({1}) at path {0:?} is invalid.")]
InvalidRequestStatus(Vec<Label>, String),

/// The certificate verification for a read_state call failed.
/// The certificate verification for a `read_state` call failed.
#[error("Certificate verification failed.")]
CertificateVerificationFailed(),

/// The signature verification for a query call failed.
#[error("Query signature verification failed.")]
QuerySignatureVerificationFailed,

/// The certificate contained a delegation that does not include the effective_canister_id in the canister_ranges field.
/// The certificate contained a delegation that does not include the `effective_canister_id` in the `canister_ranges` field.
#[error("Certificate is not authorized to respond to queries for this canister. While developing: Did you forget to set effective_canister_id?")]
CertificateNotAuthorized(),

Expand Down Expand Up @@ -211,7 +211,7 @@ impl PartialEq for AgentError {
fn eq(&self, other: &Self) -> bool {
// Verify the debug string is the same. Some of the subtypes of this error
// don't implement Eq or PartialEq, so we cannot rely on derive.
format!("{:?}", self) == format!("{:?}", other)
format!("{self:?}") == format!("{other:?}")
}
}

Expand Down Expand Up @@ -239,7 +239,7 @@ impl HttpErrorPayload {
f.write_fmt(format_args!(
"Http Error: status {}, content type {:?}, content: {}",
http::StatusCode::from_u16(self.status)
.map_or_else(|_| format!("{}", self.status), |code| format!("{}", code)),
.map_or_else(|_| format!("{}", self.status), |code| format!("{code}")),
self.content_type.clone().unwrap_or_default(),
String::from_utf8(self.content.clone()).unwrap_or_else(|_| format!(
"(unable to decode content as UTF-8: {:?})",
Expand Down
16 changes: 8 additions & 8 deletions ic-agent/src/agent/agent_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,15 @@ fn make_untimed_agent(url: &str) -> Agent {
Agent::builder()
.with_url(url)
.with_verify_query_signatures(false)
.with_ingress_expiry(Duration::from_secs(u32::MAX as _))
.with_ingress_expiry(Duration::from_secs(u32::MAX.into()))
.build()
.unwrap()
}

fn make_certifying_agent(url: &str) -> Agent {
Agent::builder()
.with_url(url)
.with_ingress_expiry(Duration::from_secs(u32::MAX as _))
.with_ingress_expiry(Duration::from_secs(u32::MAX.into()))
.build()
.unwrap()
}
Expand Down Expand Up @@ -238,7 +238,7 @@ async fn call_rejected_without_error_code() -> Result<(), AgentError> {

let (call_mock, url) = mock(
"POST",
format!("/api/v3/canister/{}/call", canister_id_str).as_str(),
format!("/api/v3/canister/{canister_id_str}/call").as_str(),
200,
body,
Some("application/cbor"),
Expand Down Expand Up @@ -475,7 +475,7 @@ async fn wrong_subnet_query_certificate() {
let response = QueryResponse::Replied {
reply: ReplyResponse { arg: blob.clone() },
signatures: vec![NodeSignature {
timestamp: 1697831349698624964,
timestamp: 1_697_831_349_698_624_964,
signature: hex::decode("4bb6ba316623395d56d8e2834ece39d2c81d47e76a9fd122e1457963be6a83a5589e2c98c7b4d8b3c6c7b11c74b8ce9dcb345b5d1bd91706a643f33c7b509b0b").unwrap(),
identity: "oo4np-rrvnz-5vram-kglex-enhkp-uew6q-vdf6z-whj4x-v44jd-tebaw-nqe".parse().unwrap()
}],
Expand Down Expand Up @@ -543,14 +543,14 @@ const RESP_WITH_SUBNET_KEY: &[u8] = include_bytes!("agent_test/with_subnet_key.b
#[cfg_attr(target_family = "wasm", wasm_bindgen_test)]
async fn too_many_delegations() {
// Use the certificate as its own delegation, and repeat the process the specified number of times
fn self_delegate_cert(subnet_id: Vec<u8>, cert: &Certificate, depth: u32) -> Certificate {
fn self_delegate_cert(subnet_id: &[u8], cert: &Certificate, depth: u32) -> Certificate {
let mut current = cert.clone();
for _ in 0..depth {
current = Certificate {
tree: current.tree.clone(),
signature: current.signature.clone(),
delegation: Some(Delegation {
subnet_id: subnet_id.clone(),
subnet_id: subnet_id.to_vec(),
certificate: serde_cbor::to_vec(&current).unwrap(),
}),
}
Expand All @@ -568,7 +568,7 @@ async fn too_many_delegations() {

let (_read_mock, url) = mock(
"POST",
format!("/api/v2/canister/{}/read_state", canister_id_str).as_str(),
format!("/api/v2/canister/{canister_id_str}/read_state").as_str(),
200,
RESP_WITH_SUBNET_KEY.into(),
Some("application/cbor"),
Expand All @@ -580,7 +580,7 @@ async fn too_many_delegations() {
.read_state_raw(vec![vec![path_label]], canister_id)
.await
.expect("read state failed");
let new_cert = self_delegate_cert(subnet_id, &cert, 1);
let new_cert = self_delegate_cert(&subnet_id, &cert, 1);
assert!(matches!(
agent.verify(&new_cert, canister_id).unwrap_err(),
AgentError::CertificateHasTooManyDelegations
Expand Down
2 changes: 1 addition & 1 deletion ic-agent/src/agent/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ impl AgentBuilder {
self.with_route_provider(url.into().parse::<Url>().unwrap())
}

/// Add a NonceFactory to this Agent. By default, no nonce is produced.
/// Add a `NonceFactory` to this Agent. By default, no nonce is produced.
pub fn with_nonce_factory(self, nonce_factory: NonceFactory) -> AgentBuilder {
self.with_nonce_generator(nonce_factory)
}
Expand Down
82 changes: 39 additions & 43 deletions ic-agent/src/agent/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -246,9 +246,8 @@ impl Agent {
return Ok(());
}
let status = self.status().await?;
let root_key = match status.root_key {
Some(key) => key,
None => return Err(AgentError::NoRootKeyInStatus(status)),
let Some(root_key) = status.root_key else {
return Err(AgentError::NoRootKeyInStatus(status));
};
self.set_root_key(root_key);
Ok(())
Expand All @@ -273,7 +272,7 @@ impl Agent {
if self.ingress_expiry.as_secs() > 90 {
rounded = rounded.replace_second(0).unwrap();
}
rounded.unix_timestamp_nanos() as u64
rounded.unix_timestamp_nanos().try_into().unwrap()
}

/// Return the principal of the identity.
Expand Down Expand Up @@ -405,7 +404,7 @@ impl Agent {
.await
}

/// Helper function for performing both the query call and possibly a read_state to check the subnet node keys.
/// Helper function for performing both the query call and possibly a `read_state` to check the subnet node keys.
///
/// This should be used instead of `query_endpoint`. No validation is performed on `signed_query`.
async fn query_inner(
Expand All @@ -430,7 +429,7 @@ impl Agent {
}
for signature in response.signatures() {
if OffsetDateTime::now_utc()
- OffsetDateTime::from_unix_timestamp_nanos(signature.timestamp as _).unwrap()
- OffsetDateTime::from_unix_timestamp_nanos(signature.timestamp.into()).unwrap()
> self.ingress_expiry
{
return Err(AgentError::CertificateOutdated(self.ingress_expiry));
Expand Down Expand Up @@ -627,7 +626,7 @@ impl Agent {
.build()
}

/// Wait for request_status to return a Replied response and return the arg.
/// Wait for `request_status` to return a Replied response and return the arg.
pub async fn wait_signed(
&self,
request_id: &RequestId,
Expand Down Expand Up @@ -678,7 +677,7 @@ impl Agent {
}
}

/// Call request_status on the RequestId in a loop and return the response as a byte vector.
/// Call `request_status` on the `RequestId` in a loop and return the response as a byte vector.
pub async fn wait(
&self,
request_id: &RequestId,
Expand Down Expand Up @@ -886,7 +885,7 @@ impl Agent {
delegation.subnet_id.as_ref(),
"public_key".as_bytes(),
];
lookup_value(&cert.tree, public_key_path).map(|pk| pk.to_vec())
lookup_value(&cert.tree, public_key_path).map(<[u8]>::to_vec)
}
}
}
Expand Down Expand Up @@ -982,9 +981,9 @@ impl Agent {
Ok((lookup_request_status(&cert, request_id)?, cert))
}

/// Send the signed request_status to the network. Will return [`RequestStatusResponse`].
/// The bytes will be checked to verify that it is a valid request_status.
/// If you want to inspect the fields of the request_status, use [`signed_request_status_inspect`] before calling this method.
/// Send the signed `request_status` to the network. Will return [`RequestStatusResponse`].
/// The bytes will be checked to verify that it is a valid `request_status`.
/// If you want to inspect the fields of the `request_status`, use [`signed_request_status_inspect`] before calling this method.
pub async fn request_status_signed(
&self,
request_id: &RequestId,
Expand All @@ -1003,7 +1002,7 @@ impl Agent {
Ok((lookup_request_status(&cert, request_id)?, cert))
}

/// Returns an UpdateBuilder enabling the construction of an update call without
/// Returns an `UpdateBuilder` enabling the construction of an update call without
/// passing all arguments.
pub fn update<S: Into<String>>(
&self,
Expand All @@ -1024,14 +1023,14 @@ impl Agent {
Status::try_from(&cbor).map_err(|_| AgentError::InvalidReplicaStatus)
}

/// Returns a QueryBuilder enabling the construction of a query call without
/// Returns a `QueryBuilder` enabling the construction of a query call without
/// passing all arguments.
pub fn query<S: Into<String>>(&self, canister_id: &Principal, method_name: S) -> QueryBuilder {
QueryBuilder::new(self, *canister_id, method_name.into())
}

/// Sign a request_status call. This will return a [`signed::SignedRequestStatus`]
/// which contains all fields of the request_status and the signed request_status in CBOR encoding
/// Sign a `request_status` call. This will return a [`signed::SignedRequestStatus`]
/// which contains all fields of the `request_status` and the signed `request_status` in CBOR encoding
pub fn sign_request_status(
&self,
effective_canister_id: Principal,
Expand Down Expand Up @@ -1068,7 +1067,7 @@ impl Agent {
}
}

/// Retrieve all existing API boundary nodes from the state tree via endpoint /api/v2/canister/<effective_canister_id>/read_state
/// Retrieve all existing API boundary nodes from the state tree via endpoint `/api/v2/canister/<effective_canister_id>/read_state`
pub async fn fetch_api_boundary_nodes_by_canister_id(
&self,
canister_id: Principal,
Expand All @@ -1079,7 +1078,7 @@ impl Agent {
Ok(api_boundary_nodes)
}

/// Retrieve all existing API boundary nodes from the state tree via endpoint /api/v2/subnet/<subnet_id>/read_state
/// Retrieve all existing API boundary nodes from the state tree via endpoint `/api/v2/subnet/<subnet_id>/read_state`
pub async fn fetch_api_boundary_nodes_by_subnet_id(
&self,
subnet_id: Principal,
Expand Down Expand Up @@ -1119,7 +1118,7 @@ impl Agent {
http_request
.headers_mut()
.insert(CONTENT_TYPE, "application/cbor".parse().unwrap());
*http_request.body_mut() = body.as_ref().cloned().map(Body::from);
*http_request.body_mut() = body.clone().map(Body::from);
Ok(http_request)
};

Expand All @@ -1137,7 +1136,7 @@ impl Agent {
// Size Check (Content-Length)
if matches!(self
.max_response_body_size
.zip(response.content_length()), Some((size_limit, content_length)) if content_length as usize > size_limit)
.zip(response.content_length()), Some((size_limit, content_length)) if content_length > size_limit as u64)
{
return Err(AgentError::ResponseSizeExceededLimit());
}
Expand Down Expand Up @@ -1170,9 +1169,7 @@ impl Agent {
endpoint: &str,
body: Option<Vec<u8>>,
) -> Result<(StatusCode, Vec<u8>), AgentError> {
let request_result = self
.request(method.clone(), endpoint, body.as_ref().cloned())
.await?;
let request_result = self.request(method.clone(), endpoint, body.clone()).await?;

let status = request_result.0;
let headers = request_result.1;
Expand All @@ -1184,13 +1181,12 @@ impl Agent {
content_type: headers
.get(CONTENT_TYPE)
.and_then(|value| value.to_str().ok())
.map(|x| x.to_string()),
.map(str::to_string),
content: body,
}))
} else if !(status == StatusCode::OK || status == StatusCode::ACCEPTED) {
Err(AgentError::InvalidHttpResponse(format!(
"Expected `200`, `202`, 4xx`, or `5xx` HTTP status code. Got: {}",
status
"Expected `200`, `202`, 4xx`, or `5xx` HTTP status code. Got: {status}",
)))
} else {
Ok((status, body))
Expand Down Expand Up @@ -1279,8 +1275,8 @@ pub fn signed_query_inspect(
if arg != *arg_cbor {
return Err(AgentError::CallDataMismatch {
field: "arg".to_string(),
value_arg: format!("{:?}", arg),
value_cbor: format!("{:?}", arg_cbor),
value_arg: format!("{arg:?}"),
value_cbor: format!("{arg_cbor:?}"),
});
}
}
Expand Down Expand Up @@ -1354,8 +1350,8 @@ pub fn signed_update_inspect(
if arg != *arg_cbor {
return Err(AgentError::CallDataMismatch {
field: "arg".to_string(),
value_arg: format!("{:?}", arg),
value_cbor: format!("{:?}", arg_cbor),
value_arg: format!("{arg:?}"),
value_cbor: format!("{arg_cbor:?}"),
});
}
}
Expand All @@ -1377,8 +1373,8 @@ pub fn signed_update_inspect(
Ok(())
}

/// Inspect the bytes to be sent as a request_status
/// Return Ok only when the bytes can be deserialized as a request_status and all fields match with the arguments
/// Inspect the bytes to be sent as a `request_status`
/// Return Ok only when the bytes can be deserialized as a `request_status` and all fields match with the arguments
pub fn signed_request_status_inspect(
sender: Principal,
request_id: &RequestId,
Expand Down Expand Up @@ -1412,8 +1408,8 @@ pub fn signed_request_status_inspect(
if paths != *paths_cbor {
return Err(AgentError::CallDataMismatch {
field: "paths".to_string(),
value_arg: format!("{:?}", paths),
value_cbor: format!("{:?}", paths_cbor),
value_arg: format!("{paths:?}"),
value_cbor: format!("{paths_cbor:?}"),
});
}
}
Expand Down Expand Up @@ -1561,13 +1557,13 @@ impl<'agent> QueryBuilder<'agent> {
self
}

/// Sets ingress_expiry_datetime to the provided timestamp, at nanosecond precision.
/// Sets `ingress_expiry_datetime` to the provided timestamp, at nanosecond precision.
pub fn expire_at(mut self, time: impl Into<OffsetDateTime>) -> Self {
self.ingress_expiry_datetime = Some(time.into().unix_timestamp_nanos() as u64);
self
}

/// Sets ingress_expiry_datetime to `max(now, 4min)`.
/// Sets `ingress_expiry_datetime` to `max(now, 4min)`.
pub fn expire_after(mut self, duration: Duration) -> Self {
self.ingress_expiry_datetime = Some(
OffsetDateTime::now_utc()
Expand Down Expand Up @@ -1602,7 +1598,7 @@ impl<'agent> QueryBuilder<'agent> {
/// Make a query call with signature verification. This will return a byte vector.
///
/// Compared with [call][Self::call], this method will **always** verify the signature of the query response
/// regardless the Agent level configuration from [AgentBuilder::with_verify_query_signatures].
/// regardless the Agent level configuration from [`AgentBuilder::with_verify_query_signatures`].
pub async fn call_with_verification(self) -> Result<Vec<u8>, AgentError> {
self.agent
.query_raw(
Expand All @@ -1620,7 +1616,7 @@ impl<'agent> QueryBuilder<'agent> {
/// Make a query call without signature verification. This will return a byte vector.
///
/// Compared with [call][Self::call], this method will **never** verify the signature of the query response
/// regardless the Agent level configuration from [AgentBuilder::with_verify_query_signatures].
/// regardless the Agent level configuration from [`AgentBuilder::with_verify_query_signatures`].
pub async fn call_without_verification(self) -> Result<Vec<u8>, AgentError> {
self.agent
.query_raw(
Expand Down Expand Up @@ -1767,13 +1763,13 @@ impl<'agent> UpdateBuilder<'agent> {
self
}

/// Sets ingress_expiry_datetime to the provided timestamp, at nanosecond precision.
/// Sets `ingress_expiry_datetime` to the provided timestamp, at nanosecond precision.
pub fn expire_at(mut self, time: impl Into<OffsetDateTime>) -> Self {
self.ingress_expiry_datetime = Some(time.into().unix_timestamp_nanos() as u64);
self
}

/// Sets ingress_expiry_datetime to `min(now, 4min)`.
/// Sets `ingress_expiry_datetime` to `min(now, 4min)`.
pub fn expire_after(mut self, duration: Duration) -> Self {
self.ingress_expiry_datetime = Some(
OffsetDateTime::now_utc()
Expand All @@ -1783,14 +1779,14 @@ impl<'agent> UpdateBuilder<'agent> {
self
}

/// Make an update call. This will call request_status on the RequestId in a loop and return
/// Make an update call. This will call `request_status` on the `RequestId` in a loop and return
/// the response as a byte vector.
pub async fn call_and_wait(self) -> Result<Vec<u8>, AgentError> {
self.call().and_wait().await.map(|x| x.0)
}

/// Make an update call. This will return a RequestId.
/// The RequestId should then be used for request_status (most likely in a loop).
/// Make an update call. This will return a `RequestId`.
/// The `RequestId` should then be used for `request_status` (most likely in a loop).
pub fn call(self) -> UpdateCall<'agent> {
let response_future = async move {
self.agent
Expand Down
Loading

0 comments on commit 75a7270

Please sign in to comment.