Skip to content

Commit

Permalink
Add the ability to specify a nonce
Browse files Browse the repository at this point in the history
  • Loading branch information
adamspofford-dfinity committed Oct 31, 2023
1 parent 56f57e6 commit c830de8
Show file tree
Hide file tree
Showing 6 changed files with 49 additions and 1 deletion.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## Unreleased

* Added node signature certification to query calls, for protection against rogue boundary nodes. This can be disabled with `with_verify_query_signatures`.
* Added `with_nonce_generation` to `QueryBuilder` for precise cache control.
* Added `read_subnet_state_raw` to `Agent` and `read_subnet_state` to `Transport` for looking up raw state by subnet ID instead of canister ID.
* Added `read_state_subnet_metrics` to `Agent` to access subnet metrics, such as total spent cycles.
* Types passed to the `to_request_id` function can now contain nested structs, signed integers, and externally tagged enums.
Expand Down
3 changes: 3 additions & 0 deletions ic-agent/src/agent/agent_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ async fn query() -> Result<(), AgentError> {
"main".to_string(),
vec![],
None,
false,
)
.await;

Expand All @@ -92,6 +93,7 @@ async fn query_error() -> Result<(), AgentError> {
"greet".to_string(),
vec![],
None,
false,
)
.await;

Expand Down Expand Up @@ -132,6 +134,7 @@ async fn query_rejected() -> Result<(), AgentError> {
"greet".to_string(),
vec![],
None,
false,
)
.await;

Expand Down
27 changes: 26 additions & 1 deletion ic-agent/src/agent/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -433,8 +433,15 @@ impl Agent {
method_name: String,
arg: Vec<u8>,
ingress_expiry_datetime: Option<u64>,
use_nonce: bool,
) -> Result<Vec<u8>, AgentError> {
let content = self.query_content(canister_id, method_name, arg, ingress_expiry_datetime)?;
let content = self.query_content(
canister_id,
method_name,
arg,
ingress_expiry_datetime,
use_nonce,
)?;
let serialized_bytes = sign_envelope(&content, self.identity.clone())?;
self.query_inner(
effective_canister_id,
Expand Down Expand Up @@ -534,13 +541,15 @@ impl Agent {
method_name: String,
arg: Vec<u8>,
ingress_expiry_datetime: Option<u64>,
use_nonce: bool,
) -> Result<EnvelopeContent, AgentError> {
Ok(EnvelopeContent::Query {
sender: self.identity.sender().map_err(AgentError::SigningError)?,
canister_id,
method_name,
arg,
ingress_expiry: ingress_expiry_datetime.unwrap_or_else(|| self.get_expiry_date()),
nonce: use_nonce.then(|| self.nonce_factory.generate()).flatten(),
})
}

Expand Down Expand Up @@ -1050,6 +1059,7 @@ pub fn signed_query_inspect(
canister_id: canister_id_cbor,
method_name: method_name_cbor,
arg: arg_cbor,
nonce: _nonce,
} => {
if ingress_expiry != *ingress_expiry_cbor {
return Err(AgentError::CallDataMismatch {
Expand Down Expand Up @@ -1310,6 +1320,7 @@ pub(crate) struct Subnet {
///
/// This makes it easier to do query calls without actually passing all arguments.
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct QueryBuilder<'agent> {
agent: &'agent Agent,
/// The [effective canister ID](https://internetcomputer.org/docs/current/references/ic-interface-spec#http-effective-canister-id) of the destination.
Expand All @@ -1322,6 +1333,8 @@ pub struct QueryBuilder<'agent> {
pub arg: Vec<u8>,
/// The Unix timestamp that the request will expire at.
pub ingress_expiry_datetime: Option<u64>,
/// Whether to include a nonce with the message.
pub use_nonce: bool,
}

impl<'agent> QueryBuilder<'agent> {
Expand All @@ -1334,6 +1347,7 @@ impl<'agent> QueryBuilder<'agent> {
method_name,
arg: vec![],
ingress_expiry_datetime: None,
use_nonce: false,
}
}

Expand Down Expand Up @@ -1368,6 +1382,13 @@ impl<'agent> QueryBuilder<'agent> {
self
}

/// Uses a nonce generated with the agent's configured nonce factory. By default queries do not use nonces,
/// and thus may get a (briefly) cached response.
pub fn with_nonce_generation(mut self) -> Self {
self.use_nonce = true;
self
}

/// Make a query call. This will return a byte vector.
pub async fn call(self) -> Result<Vec<u8>, AgentError> {
self.agent
Expand All @@ -1377,6 +1398,7 @@ impl<'agent> QueryBuilder<'agent> {
self.method_name,
self.arg,
self.ingress_expiry_datetime,
self.use_nonce,
)
.await
}
Expand All @@ -1389,6 +1411,7 @@ impl<'agent> QueryBuilder<'agent> {
self.method_name,
self.arg,
self.ingress_expiry_datetime,
self.use_nonce,
)?;
let signed_query = sign_envelope(&content, self.agent.identity.clone())?;
let EnvelopeContent::Query {
Expand All @@ -1397,6 +1420,7 @@ impl<'agent> QueryBuilder<'agent> {
canister_id,
method_name,
arg,
nonce,
} = content
else {
unreachable!()
Expand All @@ -1409,6 +1433,7 @@ impl<'agent> QueryBuilder<'agent> {
arg,
effective_canister_id: self.effective_canister_id,
signed_query,
nonce,
})
}
}
Expand Down
3 changes: 3 additions & 0 deletions ic-transport-types/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@ pub enum EnvelopeContent {
/// The argument to pass to the canister method.
#[serde(with = "serde_bytes")]
arg: Vec<u8>,
/// A random series of bytes to uniquely identify this message.
#[serde(default, skip_serializing_if = "Option::is_none", with = "serde_bytes")]
nonce: Option<Vec<u8>>,
},
}

Expand Down
6 changes: 6 additions & 0 deletions ic-transport-types/src/signed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ pub struct SignedQuery {
/// The CBOR-encoded [authentication envelope](https://internetcomputer.org/docs/current/references/ic-interface-spec#authentication) for the request.
#[serde(with = "serde_bytes")]
pub signed_query: Vec<u8>,
/// A nonce to uniquely identify this query call.
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(with = "serde_bytes")]
pub nonce: Option<Vec<u8>>,
}

/// A signed update request message. Produced by
Expand Down Expand Up @@ -87,6 +92,7 @@ mod tests {
arg: vec![0, 1],
effective_canister_id: Principal::management_canister(),
signed_query: vec![0, 1, 2, 3],
nonce: None,
};
let serialized = serde_json::to_string(&query).unwrap();
let deserialized = serde_json::from_str::<SignedQuery>(&serialized);
Expand Down
9 changes: 9 additions & 0 deletions ref-tests/tests/ic-ref.rs
Original file line number Diff line number Diff line change
Expand Up @@ -858,9 +858,18 @@ mod simple_calls {
fn query() {
with_universal_canister(|agent, canister_id| async move {
let arg = payload().reply_data(b"hello").build();
let result = agent
.query(&canister_id, "query")
.with_arg(arg.clone())
.call()
.await?;

assert_eq!(result, b"hello");

let result = agent
.query(&canister_id, "query")
.with_arg(arg)
.with_nonce_generation()
.call()
.await?;

Expand Down

0 comments on commit c830de8

Please sign in to comment.