ex3_ic_agent/agent/
mod.rs

1//! The main Agent module. Contains the [Agent] type and all associated structures.
2pub(crate) mod agent_config;
3pub mod agent_error;
4pub(crate) mod builder;
5pub mod http_transport;
6pub(crate) mod nonce;
7pub(crate) mod replica_api;
8pub(crate) mod response;
9pub(crate) mod response_authentication;
10pub mod signed;
11pub mod status;
12
13pub use agent_config::AgentConfig;
14pub use agent_error::AgentError;
15pub use builder::AgentBuilder;
16pub use nonce::{NonceFactory, NonceGenerator};
17pub use replica_api::{RejectCode, RejectResponse};
18pub use response::{Replied, RequestStatusResponse};
19
20#[cfg(test)]
21mod agent_test;
22
23use crate::{
24    agent::{
25        replica_api::{
26            CallRequestContent, Envelope, QueryContent, ReadStateContent, ReadStateResponse,
27        },
28        response_authentication::{
29            extract_der, lookup_canister_info, lookup_canister_metadata, lookup_request_status,
30            lookup_value,
31        },
32    },
33    export::Principal,
34    identity::Identity,
35    to_request_id, RequestId,
36};
37use backoff::{backoff::Backoff, ExponentialBackoffBuilder};
38use ic_certification::{Certificate, Delegation, Label};
39use serde::Serialize;
40use status::Status;
41use std::{
42    convert::TryFrom,
43    fmt,
44    future::Future,
45    pin::Pin,
46    sync::{Arc, RwLock},
47    task::{Context, Poll},
48    time::Duration,
49};
50
51const IC_REQUEST_DOMAIN_SEPARATOR: &[u8; 11] = b"\x0Aic-request";
52const IC_STATE_ROOT_DOMAIN_SEPARATOR: &[u8; 14] = b"\x0Dic-state-root";
53
54const IC_ROOT_KEY: &[u8; 133] = b"\x30\x81\x82\x30\x1d\x06\x0d\x2b\x06\x01\x04\x01\x82\xdc\x7c\x05\x03\x01\x02\x01\x06\x0c\x2b\x06\x01\x04\x01\x82\xdc\x7c\x05\x03\x02\x01\x03\x61\x00\x81\x4c\x0e\x6e\xc7\x1f\xab\x58\x3b\x08\xbd\x81\x37\x3c\x25\x5c\x3c\x37\x1b\x2e\x84\x86\x3c\x98\xa4\xf1\xe0\x8b\x74\x23\x5d\x14\xfb\x5d\x9c\x0c\xd5\x46\xd9\x68\x5f\x91\x3a\x0c\x0b\x2c\xc5\x34\x15\x83\xbf\x4b\x43\x92\xe4\x67\xdb\x96\xd6\x5b\x9b\xb4\xcb\x71\x71\x12\xf8\x47\x2e\x0d\x5a\x4d\x14\x50\x5f\xfd\x74\x84\xb0\x12\x91\x09\x1c\x5f\x87\xb9\x88\x83\x46\x3f\x98\x09\x1a\x0b\xaa\xae";
55
56#[cfg(not(target_family = "wasm"))]
57type AgentFuture<'a, V> = Pin<Box<dyn Future<Output = Result<V, AgentError>> + Send + 'a>>;
58
59#[cfg(target_family = "wasm")]
60type AgentFuture<'a, V> = Pin<Box<dyn Future<Output = Result<V, AgentError>> + 'a>>;
61
62/// A facade that connects to a Replica and does requests. These requests can be of any type
63/// (does not have to be HTTP). This trait is to inverse the control from the Agent over its
64/// connection code, and to resolve any direct dependencies to tokio or HTTP code from this
65/// crate.
66///
67/// An implementation of this trait for HTTP transport is implemented using Reqwest, with the
68/// feature flag `reqwest`. This might be deprecated in the future.
69///
70/// Any error returned by these methods will bubble up to the code that called the [Agent].
71pub trait Transport: Send + Sync {
72    /// Sends an asynchronous request to a Replica. The Request ID is non-mutable and
73    /// depends on the content of the envelope.
74    ///
75    /// This normally corresponds to the `/api/v2/canister/<effective_canister_id>/call` endpoint.
76    fn call(
77        &self,
78        effective_canister_id: Principal,
79        envelope: Vec<u8>,
80        request_id: RequestId,
81    ) -> AgentFuture<()>;
82
83    /// Sends a synchronous request to a Replica. This call includes the body of the request message
84    /// itself (envelope).
85    ///
86    /// This normally corresponds to the `/api/v2/canister/<effective_canister_id>/read_state` endpoint.
87    fn read_state(
88        &self,
89        effective_canister_id: Principal,
90        envelope: Vec<u8>,
91    ) -> AgentFuture<Vec<u8>>;
92
93    /// Sends a synchronous request to a Replica. This call includes the body of the request message
94    /// itself (envelope).
95    ///
96    /// This normally corresponds to the `/api/v2/canister/<effective_canister_id>/query` endpoint.
97    fn query(&self, effective_canister_id: Principal, envelope: Vec<u8>) -> AgentFuture<Vec<u8>>;
98
99    /// Sends a status request to the Replica, returning whatever the replica returns.
100    /// In the current spec v2, this is a CBOR encoded status message, but we are not
101    /// making this API attach semantics to the response.
102    fn status(&self) -> AgentFuture<Vec<u8>>;
103}
104
105impl<I: Transport + ?Sized> Transport for Box<I> {
106    fn call(
107        &self,
108        effective_canister_id: Principal,
109        envelope: Vec<u8>,
110        request_id: RequestId,
111    ) -> AgentFuture<()> {
112        (**self).call(effective_canister_id, envelope, request_id)
113    }
114    fn read_state(
115        &self,
116        effective_canister_id: Principal,
117        envelope: Vec<u8>,
118    ) -> AgentFuture<Vec<u8>> {
119        (**self).read_state(effective_canister_id, envelope)
120    }
121    fn query(&self, effective_canister_id: Principal, envelope: Vec<u8>) -> AgentFuture<Vec<u8>> {
122        (**self).query(effective_canister_id, envelope)
123    }
124    fn status(&self) -> AgentFuture<Vec<u8>> {
125        (**self).status()
126    }
127}
128impl<I: Transport + ?Sized> Transport for Arc<I> {
129    fn call(
130        &self,
131        effective_canister_id: Principal,
132        envelope: Vec<u8>,
133        request_id: RequestId,
134    ) -> AgentFuture<()> {
135        (**self).call(effective_canister_id, envelope, request_id)
136    }
137    fn read_state(
138        &self,
139        effective_canister_id: Principal,
140        envelope: Vec<u8>,
141    ) -> AgentFuture<Vec<u8>> {
142        (**self).read_state(effective_canister_id, envelope)
143    }
144    fn query(&self, effective_canister_id: Principal, envelope: Vec<u8>) -> AgentFuture<Vec<u8>> {
145        (**self).query(effective_canister_id, envelope)
146    }
147    fn status(&self) -> AgentFuture<Vec<u8>> {
148        (**self).status()
149    }
150}
151
152/// Classification of the result of a request_status_raw (poll) call.
153#[derive(Debug)]
154pub enum PollResult {
155    /// The request has been submitted, but we do not know yet if it
156    /// has been accepted or not.
157    Submitted,
158
159    /// The request has been received and may be processing.
160    Accepted,
161
162    /// The request completed and returned some data.
163    Completed(Vec<u8>),
164}
165
166/// A low level Agent to make calls to a Replica endpoint.
167///
168/// ```ignore
169/// # // This test is ignored because it requires an ic to be running. We run these
170/// # // in the ic-ref workflow.
171/// use ic_agent::{Agent, export::Principal};
172/// use candid::{Encode, Decode, CandidType, Nat};
173/// use serde::Deserialize;
174///
175/// #[derive(CandidType)]
176/// struct Argument {
177///   amount: Option<Nat>,
178/// }
179///
180/// #[derive(CandidType, Deserialize)]
181/// struct CreateCanisterResult {
182///   canister_id: Principal,
183/// }
184///
185/// # fn create_identity() -> impl ic_agent::Identity {
186/// #     let rng = ring::rand::SystemRandom::new();
187/// #     let key_pair = ring::signature::Ed25519KeyPair::generate_pkcs8(&rng)
188/// #         .expect("Could not generate a key pair.");
189/// #
190/// #     ic_agent::identity::BasicIdentity::from_key_pair(
191/// #         ring::signature::Ed25519KeyPair::from_pkcs8(key_pair.as_ref())
192/// #           .expect("Could not read the key pair."),
193/// #     )
194/// # }
195/// #
196/// # const URL: &'static str = concat!("http://localhost:", env!("IC_REF_PORT"));
197/// #
198/// async fn create_a_canister() -> Result<Principal, Box<dyn std::error::Error>> {
199///   let agent = Agent::builder()
200///     .with_url(URL)
201///     .with_identity(create_identity())
202///     .build()?;
203///
204///   // Only do the following call when not contacting the IC main net (e.g. a local emulator).
205///   // This is important as the main net public key is static and a rogue network could return
206///   // a different key.
207///   // If you know the root key ahead of time, you can use `agent.set_root_key(root_key);`.
208///   agent.fetch_root_key().await?;
209///   let management_canister_id = Principal::from_text("aaaaa-aa")?;
210///
211///   // Create a call to the management canister to create a new canister ID,
212///   // and wait for a result.
213///   // The effective canister id must belong to the canister ranges of the subnet at which the canister is created.
214///   let effective_canister_id = Principal::from_text("rwlgt-iiaaa-aaaaa-aaaaa-cai").unwrap();
215///   let response = agent.update(&management_canister_id, "provisional_create_canister_with_cycles")
216///     .with_effective_canister_id(effective_canister_id)
217///     .with_arg(&Encode!(&Argument { amount: None })?)
218///     .call_and_wait()
219///     .await?;
220///
221///   let result = Decode!(response.as_slice(), CreateCanisterResult)?;
222///   let canister_id: Principal = Principal::from_text(&result.canister_id.to_text())?;
223///   Ok(canister_id)
224/// }
225///
226/// # let mut runtime = tokio::runtime::Runtime::new().unwrap();
227/// # runtime.block_on(async {
228/// let canister_id = create_a_canister().await.unwrap();
229/// eprintln!("{}", canister_id);
230/// # });
231/// ```
232///
233/// This agent does not understand Candid, and only acts on byte buffers.
234#[derive(Clone)]
235pub struct Agent {
236    nonce_factory: Arc<dyn NonceGenerator>,
237    identity: Arc<dyn Identity>,
238    ingress_expiry: Duration,
239    root_key: Arc<RwLock<Vec<u8>>>,
240    transport: Arc<dyn Transport>,
241}
242
243impl fmt::Debug for Agent {
244    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
245        f.debug_struct("Agent")
246            .field("ingress_expiry", &self.ingress_expiry)
247            .finish_non_exhaustive()
248    }
249}
250
251impl Agent {
252    /// Create an instance of an [`AgentBuilder`] for building an [`Agent`]. This is simpler than
253    /// using the [`AgentConfig`] and [`Agent::new()`].
254    pub fn builder() -> builder::AgentBuilder {
255        Default::default()
256    }
257
258    /// Create an instance of an [`Agent`].
259    pub fn new(config: agent_config::AgentConfig) -> Result<Agent, AgentError> {
260        Ok(Agent {
261            nonce_factory: config.nonce_factory,
262            identity: config.identity,
263            ingress_expiry: config
264                .ingress_expiry
265                .unwrap_or_else(|| Duration::from_secs(300)),
266            root_key: Arc::new(RwLock::new(IC_ROOT_KEY.to_vec())),
267            transport: config
268                .transport
269                .ok_or_else(AgentError::MissingReplicaTransport)?,
270        })
271    }
272
273    /// Set the transport of the [`Agent`].
274    pub fn set_transport<F: 'static + Transport>(&mut self, transport: F) {
275        self.transport = Arc::new(transport);
276    }
277
278    /// Set the identity provider for signing messages.
279    ///
280    /// NOTE: if you change the identity while having update calls in
281    /// flight, you will not be able to [Agent::poll] the status of these
282    /// messages.
283    pub fn set_identity<I>(&mut self, identity: I)
284    where
285        I: 'static + Identity,
286    {
287        self.identity = Arc::new(identity);
288    }
289
290    /// By default, the agent is configured to talk to the main Internet Computer, and verifies
291    /// responses using a hard-coded public key.
292    ///
293    /// This function will instruct the agent to ask the endpoint for its public key, and use
294    /// that instead. This is required when talking to a local test instance, for example.
295    ///
296    /// *Only use this when you are  _not_ talking to the main Internet Computer, otherwise
297    /// you are prone to man-in-the-middle attacks! Do not call this function by default.*
298    pub async fn fetch_root_key(&self) -> Result<(), AgentError> {
299        if self.read_root_key() != IC_ROOT_KEY.to_vec() {
300            // already fetched the root key
301            return Ok(());
302        }
303        let status = self.status().await?;
304        let root_key = status
305            .root_key
306            .clone()
307            .ok_or(AgentError::NoRootKeyInStatus(status))?;
308        self.set_root_key(root_key);
309        Ok(())
310    }
311
312    /// By default, the agent is configured to talk to the main Internet Computer, and verifies
313    /// responses using a hard-coded public key.
314    ///
315    /// Using this function you can set the root key to a known one if you know if beforehand.
316    pub fn set_root_key(&self, root_key: Vec<u8>) {
317        *self.root_key.write().unwrap() = root_key;
318    }
319
320    /// Return the root key currently in use.
321    pub fn read_root_key(&self) -> Vec<u8> {
322        self.root_key.read().unwrap().clone()
323    }
324
325    fn get_expiry_date(&self) -> u64 {
326        // TODO(hansl): evaluate if we need this on the agent side (my hunch is we don't).
327        let permitted_drift = Duration::from_secs(60);
328        (self
329            .ingress_expiry
330            .saturating_add({
331                #[cfg(not(target_family = "wasm"))]
332                {
333                    std::time::SystemTime::now()
334                        .duration_since(std::time::UNIX_EPOCH)
335                        .expect("Time wrapped around.")
336                }
337                #[cfg(all(target_family = "wasm", feature = "wasm-bindgen"))]
338                {
339                    Duration::from_nanos((js_sys::Date::now() * 1_000_000.) as _)
340                }
341            })
342            .saturating_sub(permitted_drift))
343        .as_nanos() as u64
344    }
345
346    /// Return the principal of the identity.
347    pub fn get_principal(&self) -> Result<Principal, String> {
348        self.identity.sender()
349    }
350
351    async fn query_endpoint<A>(
352        &self,
353        effective_canister_id: Principal,
354        serialized_bytes: Vec<u8>,
355    ) -> Result<A, AgentError>
356    where
357        A: serde::de::DeserializeOwned,
358    {
359        let bytes = self
360            .transport
361            .query(effective_canister_id, serialized_bytes)
362            .await?;
363        serde_cbor::from_slice(&bytes).map_err(AgentError::InvalidCborData)
364    }
365
366    async fn read_state_endpoint<A>(
367        &self,
368        effective_canister_id: Principal,
369        serialized_bytes: Vec<u8>,
370    ) -> Result<A, AgentError>
371    where
372        A: serde::de::DeserializeOwned,
373    {
374        let bytes = self
375            .transport
376            .read_state(effective_canister_id, serialized_bytes)
377            .await?;
378        serde_cbor::from_slice(&bytes).map_err(AgentError::InvalidCborData)
379    }
380
381    async fn call_endpoint(
382        &self,
383        effective_canister_id: Principal,
384        request_id: RequestId,
385        serialized_bytes: Vec<u8>,
386    ) -> Result<RequestId, AgentError> {
387        self.transport
388            .call(effective_canister_id, serialized_bytes, request_id)
389            .await?;
390        Ok(request_id)
391    }
392
393    /// The simplest way to do a query call; sends a byte array and will return a byte vector.
394    /// The encoding is left as an exercise to the user.
395    async fn query_raw(
396        &self,
397        canister_id: &Principal,
398        effective_canister_id: Principal,
399        method_name: &str,
400        arg: &[u8],
401        ingress_expiry_datetime: Option<u64>,
402    ) -> Result<Vec<u8>, AgentError> {
403        let request = self.query_content(canister_id, method_name, arg, ingress_expiry_datetime)?;
404        let serialized_bytes = sign_request(&request, self.identity.clone())?;
405        self.query_endpoint::<replica_api::QueryResponse>(effective_canister_id, serialized_bytes)
406            .await
407            .and_then(|response| match response {
408                replica_api::QueryResponse::Replied { reply } => Ok(reply.arg),
409                replica_api::QueryResponse::Rejected(response) => {
410                    Err(AgentError::ReplicaError(response))
411                }
412            })
413    }
414
415    /// Send the signed query to the network. Will return a byte vector.
416    /// The bytes will be checked if it is a valid query.
417    /// If you want to inspect the fields of the query call, use [`signed_query_inspect`] before calling this method.
418    pub async fn query_signed(
419        &self,
420        effective_canister_id: Principal,
421        signed_query: Vec<u8>,
422    ) -> Result<Vec<u8>, AgentError> {
423        let _envelope: Envelope<QueryContent> =
424            serde_cbor::from_slice(&signed_query).map_err(AgentError::InvalidCborData)?;
425        self.query_endpoint::<replica_api::QueryResponse>(effective_canister_id, signed_query)
426            .await
427            .and_then(|response| match response {
428                replica_api::QueryResponse::Replied { reply } => Ok(reply.arg),
429                replica_api::QueryResponse::Rejected(response) => {
430                    Err(AgentError::ReplicaError(response))
431                }
432            })
433    }
434
435    fn query_content(
436        &self,
437        canister_id: &Principal,
438        method_name: &str,
439        arg: &[u8],
440        ingress_expiry_datetime: Option<u64>,
441    ) -> Result<QueryContent, AgentError> {
442        Ok(QueryContent::QueryRequest {
443            sender: self.identity.sender().map_err(AgentError::SigningError)?,
444            canister_id: *canister_id,
445            method_name: method_name.to_string(),
446            arg: arg.to_vec(),
447            ingress_expiry: ingress_expiry_datetime.unwrap_or_else(|| self.get_expiry_date()),
448        })
449    }
450
451    /// The simplest way to do an update call; sends a byte array and will return a RequestId.
452    /// The RequestId should then be used for request_status (most likely in a loop).
453    async fn update_raw(
454        &self,
455        canister_id: &Principal,
456        effective_canister_id: Principal,
457        method_name: &str,
458        arg: &[u8],
459        ingress_expiry_datetime: Option<u64>,
460    ) -> Result<RequestId, AgentError> {
461        let request =
462            self.update_content(canister_id, method_name, arg, ingress_expiry_datetime)?;
463        let request_id = to_request_id(&request)?;
464        let serialized_bytes = sign_request(&request, self.identity.clone())?;
465
466        self.call_endpoint(effective_canister_id, request_id, serialized_bytes)
467            .await
468    }
469
470    /// Send the signed update to the network. Will return a [`RequestId`].
471    /// The bytes will be checked to verify that it is a valid update.
472    /// If you want to inspect the fields of the update, use [`signed_update_inspect`] before calling this method.
473    pub async fn update_signed(
474        &self,
475        effective_canister_id: Principal,
476        signed_update: Vec<u8>,
477    ) -> Result<RequestId, AgentError> {
478        let envelope: Envelope<CallRequestContent> =
479            serde_cbor::from_slice(&signed_update).map_err(AgentError::InvalidCborData)?;
480        let request_id = to_request_id(&envelope.content)?;
481        self.call_endpoint(effective_canister_id, request_id, signed_update)
482            .await
483    }
484
485    fn update_content(
486        &self,
487        canister_id: &Principal,
488        method_name: &str,
489        arg: &[u8],
490        ingress_expiry_datetime: Option<u64>,
491    ) -> Result<CallRequestContent, AgentError> {
492        Ok(CallRequestContent::CallRequest {
493            canister_id: *canister_id,
494            method_name: method_name.into(),
495            arg: arg.to_vec(),
496            nonce: self.nonce_factory.generate().map(|b| b.as_slice().into()),
497            sender: self.identity.sender().map_err(AgentError::SigningError)?,
498            ingress_expiry: ingress_expiry_datetime.unwrap_or_else(|| self.get_expiry_date()),
499        })
500    }
501
502    /// Call request_status on the RequestId once and classify the result
503    pub async fn poll(
504        &self,
505        request_id: &RequestId,
506        effective_canister_id: Principal,
507    ) -> Result<PollResult, AgentError> {
508        match self
509            .request_status_raw(request_id, effective_canister_id)
510            .await?
511        {
512            RequestStatusResponse::Unknown => Ok(PollResult::Submitted),
513
514            RequestStatusResponse::Received | RequestStatusResponse::Processing => {
515                Ok(PollResult::Accepted)
516            }
517
518            RequestStatusResponse::Replied {
519                reply: Replied::CallReplied(arg),
520            } => Ok(PollResult::Completed(arg)),
521
522            RequestStatusResponse::Rejected(response) => Err(AgentError::ReplicaError(response)),
523
524            RequestStatusResponse::Done => Err(AgentError::RequestStatusDoneNoReply(String::from(
525                *request_id,
526            ))),
527        }
528    }
529
530    /// Call request_status on the RequestId in a loop and return the response as a byte vector.
531    pub async fn wait(
532        &self,
533        request_id: RequestId,
534        effective_canister_id: Principal,
535    ) -> Result<Vec<u8>, AgentError> {
536        let mut retry_policy = ExponentialBackoffBuilder::new()
537            .with_initial_interval(Duration::from_millis(500))
538            .with_max_interval(Duration::from_secs(1))
539            .with_multiplier(1.4)
540            .with_max_elapsed_time(Some(Duration::from_secs(60 * 5)))
541            .build();
542        let mut request_accepted = false;
543        loop {
544            match self.poll(&request_id, effective_canister_id).await? {
545                PollResult::Submitted => {}
546                PollResult::Accepted => {
547                    if !request_accepted {
548                        // The system will return RequestStatusResponse::Unknown
549                        // (PollResult::Submitted) until the request is accepted
550                        // and we generally cannot know how long that will take.
551                        // State transitions between Received and Processing may be
552                        // instantaneous. Therefore, once we know the request is accepted,
553                        // we should restart the backoff so the request does not time out.
554
555                        retry_policy.reset();
556                        request_accepted = true;
557                    }
558                }
559                PollResult::Completed(result) => return Ok(result),
560            };
561
562            match retry_policy.next_backoff() {
563                #[cfg(not(target_family = "wasm"))]
564                Some(duration) => tokio::time::sleep(duration).await,
565                #[cfg(all(target_family = "wasm", feature = "wasm-bindgen"))]
566                Some(duration) => {
567                    wasm_bindgen_futures::JsFuture::from(js_sys::Promise::new(&mut |rs, rj| {
568                        if let Err(e) = web_sys::window()
569                            .expect("global window unavailable")
570                            .set_timeout_with_callback_and_timeout_and_arguments_0(
571                                &rs,
572                                duration.as_millis() as _,
573                            )
574                        {
575                            use wasm_bindgen::UnwrapThrowExt;
576                            rj.call1(&rj, &e).unwrap_throw();
577                        }
578                    }))
579                    .await
580                    .expect("unable to setTimeout");
581                }
582                None => return Err(AgentError::TimeoutWaitingForResponse()),
583            }
584        }
585    }
586
587    /// Request the raw state tree directly. See [the protocol docs](https://smartcontracts.org/docs/interface-spec/index.html#http-read-state) for more information.
588    pub async fn read_state_raw(
589        &self,
590        paths: Vec<Vec<Label>>,
591        effective_canister_id: Principal,
592    ) -> Result<Certificate, AgentError> {
593        let request = self.read_state_content(paths)?;
594        let serialized_bytes = sign_request(&request, self.identity.clone())?;
595
596        let read_state_response: ReadStateResponse = self
597            .read_state_endpoint(effective_canister_id, serialized_bytes)
598            .await?;
599        let cert: Certificate = serde_cbor::from_slice(&read_state_response.certificate)
600            .map_err(AgentError::InvalidCborData)?;
601        self.verify(&cert, effective_canister_id)?;
602        Ok(cert)
603    }
604
605    fn read_state_content(&self, paths: Vec<Vec<Label>>) -> Result<ReadStateContent, AgentError> {
606        Ok(ReadStateContent::ReadStateRequest {
607            sender: self.identity.sender().map_err(AgentError::SigningError)?,
608            paths,
609            ingress_expiry: self.get_expiry_date(),
610        })
611    }
612
613    /// Verify a certificate, checking delegation if present.
614    /// Only passes if the certificate also has authority over the canister.
615    pub fn verify(
616        &self,
617        cert: &Certificate,
618        effective_canister_id: Principal,
619    ) -> Result<(), AgentError> {
620        let sig = &cert.signature;
621
622        let root_hash = cert.tree.digest();
623        let mut msg = vec![];
624        msg.extend_from_slice(IC_STATE_ROOT_DOMAIN_SEPARATOR);
625        msg.extend_from_slice(&root_hash);
626
627        let der_key = self.check_delegation(&cert.delegation, effective_canister_id)?;
628        let key = extract_der(der_key)?;
629
630        ic_verify_bls_signature::verify_bls_signature(sig, &msg, &key)
631            .map_err(|_| AgentError::CertificateVerificationFailed())
632    }
633
634    fn check_delegation(
635        &self,
636        delegation: &Option<Delegation>,
637        effective_canister_id: Principal,
638    ) -> Result<Vec<u8>, AgentError> {
639        match delegation {
640            None => Ok(self.read_root_key()),
641            Some(delegation) => {
642                let cert: Certificate = serde_cbor::from_slice(&delegation.certificate)
643                    .map_err(AgentError::InvalidCborData)?;
644                self.verify(&cert, effective_canister_id)?;
645                let canister_range_lookup = [
646                    "subnet".as_bytes(),
647                    delegation.subnet_id.as_ref(),
648                    "canister_ranges".as_bytes(),
649                ];
650                let canister_range = lookup_value(&cert, canister_range_lookup)?;
651                let ranges: Vec<(Principal, Principal)> =
652                    serde_cbor::from_slice(canister_range).map_err(AgentError::InvalidCborData)?;
653                if !principal_is_within_ranges(&effective_canister_id, &ranges[..]) {
654                    // the certificate is not authorized to answer calls for this canister
655                    return Err(AgentError::CertificateNotAuthorized());
656                }
657
658                let public_key_path = [
659                    "subnet".as_bytes(),
660                    delegation.subnet_id.as_ref(),
661                    "public_key".as_bytes(),
662                ];
663                lookup_value(&cert, public_key_path).map(|pk| pk.to_vec())
664            }
665        }
666    }
667
668    /// Request information about a particular canister for a single state subkey. See [the protocol docs](https://smartcontracts.org/docs/interface-spec/index.html#state-tree-canister-information) for more information.
669    pub async fn read_state_canister_info(
670        &self,
671        canister_id: Principal,
672        path: &str,
673    ) -> Result<Vec<u8>, AgentError> {
674        let paths: Vec<Vec<Label>> = vec![vec![
675            "canister".into(),
676            Label::from_bytes(canister_id.as_slice()),
677            path.into(),
678        ]];
679
680        let cert = self.read_state_raw(paths, canister_id).await?;
681
682        lookup_canister_info(cert, canister_id, path)
683    }
684
685    /// Request the bytes of the canister's custom section `icp:public <path>` or `icp:private <path>`.
686    pub async fn read_state_canister_metadata(
687        &self,
688        canister_id: Principal,
689        path: &str,
690    ) -> Result<Vec<u8>, AgentError> {
691        let paths: Vec<Vec<Label>> = vec![vec![
692            "canister".into(),
693            Label::from_bytes(canister_id.as_slice()),
694            "metadata".into(),
695            path.into(),
696        ]];
697
698        let cert = self.read_state_raw(paths, canister_id).await?;
699
700        lookup_canister_metadata(cert, canister_id, path)
701    }
702
703    /// Fetches the status of a particular request by its ID.
704    pub async fn request_status_raw(
705        &self,
706        request_id: &RequestId,
707        effective_canister_id: Principal,
708    ) -> Result<RequestStatusResponse, AgentError> {
709        let paths: Vec<Vec<Label>> =
710            vec![vec!["request_status".into(), request_id.to_vec().into()]];
711
712        let cert = self.read_state_raw(paths, effective_canister_id).await?;
713
714        lookup_request_status(cert, request_id)
715    }
716
717    /// Send the signed request_status to the network. Will return [`RequestStatusResponse`].
718    /// The bytes will be checked to verify that it is a valid request_status.
719    /// If you want to inspect the fields of the request_status, use [`signed_request_status_inspect`] before calling this method.
720    pub async fn request_status_signed(
721        &self,
722        request_id: &RequestId,
723        effective_canister_id: Principal,
724        signed_request_status: Vec<u8>,
725    ) -> Result<RequestStatusResponse, AgentError> {
726        let _envelope: Envelope<ReadStateContent> =
727            serde_cbor::from_slice(&signed_request_status).map_err(AgentError::InvalidCborData)?;
728        let read_state_response: ReadStateResponse = self
729            .read_state_endpoint(effective_canister_id, signed_request_status)
730            .await?;
731
732        let cert: Certificate = serde_cbor::from_slice(&read_state_response.certificate)
733            .map_err(AgentError::InvalidCborData)?;
734        self.verify(&cert, effective_canister_id)?;
735        lookup_request_status(cert, request_id)
736    }
737
738    /// Returns an UpdateBuilder enabling the construction of an update call without
739    /// passing all arguments.
740    pub fn update<S: Into<String>>(
741        &self,
742        canister_id: &Principal,
743        method_name: S,
744    ) -> UpdateBuilder {
745        UpdateBuilder::new(self, *canister_id, method_name.into())
746    }
747
748    /// Calls and returns the information returned by the status endpoint of a replica.
749    pub async fn status(&self) -> Result<Status, AgentError> {
750        let bytes = self.transport.status().await?;
751
752        let cbor: serde_cbor::Value =
753            serde_cbor::from_slice(&bytes).map_err(AgentError::InvalidCborData)?;
754
755        Status::try_from(&cbor).map_err(|_| AgentError::InvalidReplicaStatus)
756    }
757
758    /// Returns a QueryBuilder enabling the construction of a query call without
759    /// passing all arguments.
760    pub fn query<S: Into<String>>(&self, canister_id: &Principal, method_name: S) -> QueryBuilder {
761        QueryBuilder::new(self, *canister_id, method_name.into())
762    }
763
764    /// Sign a request_status call. This will return a [`signed::SignedRequestStatus`]
765    /// which contains all fields of the request_status and the signed request_status in CBOR encoding
766    pub fn sign_request_status(
767        &self,
768        effective_canister_id: Principal,
769        request_id: RequestId,
770    ) -> Result<signed::SignedRequestStatus, AgentError> {
771        let paths: Vec<Vec<Label>> =
772            vec![vec!["request_status".into(), request_id.to_vec().into()]];
773        let read_state_content = self.read_state_content(paths)?;
774        let signed_request_status = sign_request(&read_state_content, self.identity.clone())?;
775        match read_state_content {
776            ReadStateContent::ReadStateRequest {
777                ingress_expiry,
778                sender,
779                paths: _path,
780            } => Ok(signed::SignedRequestStatus {
781                ingress_expiry,
782                sender,
783                effective_canister_id,
784                request_id,
785                signed_request_status,
786            }),
787        }
788    }
789}
790
791// Checks if a principal is contained within a list of principal ranges
792// A range is a tuple: (low: Principal, high: Principal), as described here: https://docs.dfinity.systems/spec/public/#state-tree-subnet
793fn principal_is_within_ranges(principal: &Principal, ranges: &[(Principal, Principal)]) -> bool {
794    ranges
795        .iter()
796        .any(|r| principal >= &r.0 && principal <= &r.1)
797}
798
799fn construct_message(request_id: &RequestId) -> Vec<u8> {
800    let mut buf = vec![];
801    buf.extend_from_slice(IC_REQUEST_DOMAIN_SEPARATOR);
802    buf.extend_from_slice(request_id.as_slice());
803    buf
804}
805
806fn sign_request<'a, V>(request: &V, identity: Arc<dyn Identity>) -> Result<Vec<u8>, AgentError>
807where
808    V: 'a + Serialize,
809{
810    let request_id = to_request_id(&request)?;
811    let msg = construct_message(&request_id);
812    let signature = identity.sign(&msg).map_err(AgentError::SigningError)?;
813
814    let envelope = Envelope {
815        content: request,
816        sender_pubkey: signature.public_key,
817        sender_sig: signature.signature,
818    };
819
820    let mut serialized_bytes = Vec::new();
821    let mut serializer = serde_cbor::Serializer::new(&mut serialized_bytes);
822    serializer.self_describe()?;
823    envelope.serialize(&mut serializer)?;
824
825    Ok(serialized_bytes)
826}
827
828/// Inspect the bytes to be sent as a query
829/// Return Ok only when the bytes can be deserialized as a query and all fields match with the arguments
830pub fn signed_query_inspect(
831    sender: Principal,
832    canister_id: Principal,
833    method_name: &str,
834    arg: &[u8],
835    ingress_expiry: u64,
836    signed_query: Vec<u8>,
837) -> Result<(), AgentError> {
838    let envelope: Envelope<QueryContent> =
839        serde_cbor::from_slice(&signed_query).map_err(AgentError::InvalidCborData)?;
840    match envelope.content {
841        QueryContent::QueryRequest {
842            ingress_expiry: ingress_expiry_cbor,
843            sender: sender_cbor,
844            canister_id: canister_id_cbor,
845            method_name: method_name_cbor,
846            arg: arg_cbor,
847        } => {
848            if ingress_expiry != ingress_expiry_cbor {
849                return Err(AgentError::CallDataMismatch {
850                    field: "ingress_expiry".to_string(),
851                    value_arg: ingress_expiry.to_string(),
852                    value_cbor: ingress_expiry_cbor.to_string(),
853                });
854            }
855            if sender != sender_cbor {
856                return Err(AgentError::CallDataMismatch {
857                    field: "sender".to_string(),
858                    value_arg: sender.to_string(),
859                    value_cbor: sender_cbor.to_string(),
860                });
861            }
862            if canister_id != canister_id_cbor {
863                return Err(AgentError::CallDataMismatch {
864                    field: "canister_id".to_string(),
865                    value_arg: canister_id.to_string(),
866                    value_cbor: canister_id_cbor.to_string(),
867                });
868            }
869            if method_name != method_name_cbor {
870                return Err(AgentError::CallDataMismatch {
871                    field: "method_name".to_string(),
872                    value_arg: method_name.to_string(),
873                    value_cbor: method_name_cbor,
874                });
875            }
876            if arg != arg_cbor {
877                return Err(AgentError::CallDataMismatch {
878                    field: "arg".to_string(),
879                    value_arg: format!("{:?}", arg),
880                    value_cbor: format!("{:?}", arg_cbor),
881                });
882            }
883        }
884    }
885    Ok(())
886}
887
888/// Inspect the bytes to be sent as an update
889/// Return Ok only when the bytes can be deserialized as an update and all fields match with the arguments
890pub fn signed_update_inspect(
891    sender: Principal,
892    canister_id: Principal,
893    method_name: &str,
894    arg: &[u8],
895    ingress_expiry: u64,
896    signed_update: Vec<u8>,
897) -> Result<(), AgentError> {
898    let envelope: Envelope<CallRequestContent> =
899        serde_cbor::from_slice(&signed_update).map_err(AgentError::InvalidCborData)?;
900    match envelope.content {
901        CallRequestContent::CallRequest {
902            nonce: _nonce,
903            ingress_expiry: ingress_expiry_cbor,
904            sender: sender_cbor,
905            canister_id: canister_id_cbor,
906            method_name: method_name_cbor,
907            arg: arg_cbor,
908        } => {
909            if ingress_expiry != ingress_expiry_cbor {
910                return Err(AgentError::CallDataMismatch {
911                    field: "ingress_expiry".to_string(),
912                    value_arg: ingress_expiry.to_string(),
913                    value_cbor: ingress_expiry_cbor.to_string(),
914                });
915            }
916            if sender != sender_cbor {
917                return Err(AgentError::CallDataMismatch {
918                    field: "sender".to_string(),
919                    value_arg: sender.to_string(),
920                    value_cbor: sender_cbor.to_string(),
921                });
922            }
923            if canister_id != canister_id_cbor {
924                return Err(AgentError::CallDataMismatch {
925                    field: "canister_id".to_string(),
926                    value_arg: canister_id.to_string(),
927                    value_cbor: canister_id_cbor.to_string(),
928                });
929            }
930            if method_name != method_name_cbor {
931                return Err(AgentError::CallDataMismatch {
932                    field: "method_name".to_string(),
933                    value_arg: method_name.to_string(),
934                    value_cbor: method_name_cbor,
935                });
936            }
937            if arg != arg_cbor {
938                return Err(AgentError::CallDataMismatch {
939                    field: "arg".to_string(),
940                    value_arg: format!("{:?}", arg),
941                    value_cbor: format!("{:?}", arg_cbor),
942                });
943            }
944        }
945    }
946    Ok(())
947}
948
949/// Inspect the bytes to be sent as a request_status
950/// Return Ok only when the bytes can be deserialized as a request_status and all fields match with the arguments
951pub fn signed_request_status_inspect(
952    sender: Principal,
953    request_id: &RequestId,
954    ingress_expiry: u64,
955    signed_request_status: Vec<u8>,
956) -> Result<(), AgentError> {
957    let paths: Vec<Vec<Label>> = vec![vec!["request_status".into(), request_id.to_vec().into()]];
958    let envelope: Envelope<ReadStateContent> =
959        serde_cbor::from_slice(&signed_request_status).map_err(AgentError::InvalidCborData)?;
960    match envelope.content {
961        ReadStateContent::ReadStateRequest {
962            ingress_expiry: ingress_expiry_cbor,
963            sender: sender_cbor,
964            paths: paths_cbor,
965        } => {
966            if ingress_expiry != ingress_expiry_cbor {
967                return Err(AgentError::CallDataMismatch {
968                    field: "ingress_expiry".to_string(),
969                    value_arg: ingress_expiry.to_string(),
970                    value_cbor: ingress_expiry_cbor.to_string(),
971                });
972            }
973            if sender != sender_cbor {
974                return Err(AgentError::CallDataMismatch {
975                    field: "sender".to_string(),
976                    value_arg: sender.to_string(),
977                    value_cbor: sender_cbor.to_string(),
978                });
979            }
980
981            if paths != paths_cbor {
982                return Err(AgentError::CallDataMismatch {
983                    field: "paths".to_string(),
984                    value_arg: format!("{:?}", paths),
985                    value_cbor: format!("{:?}", paths_cbor),
986                });
987            }
988        }
989    }
990    Ok(())
991}
992
993/// A Query Request Builder.
994///
995/// This makes it easier to do query calls without actually passing all arguments.
996#[derive(Debug)]
997pub struct QueryBuilder<'agent> {
998    agent: &'agent Agent,
999    /// The [effective canister ID](https://smartcontracts.org/docs/interface-spec/index.html#http-effective-canister-id) of the destination.
1000    pub effective_canister_id: Principal,
1001    /// The principal ID of the canister being called.
1002    pub canister_id: Principal,
1003    /// The name of the canister method being called.
1004    pub method_name: String,
1005    /// The argument blob to be passed to the method.
1006    pub arg: Vec<u8>,
1007    /// The Unix timestamp that the request will expire at.
1008    pub ingress_expiry_datetime: Option<u64>,
1009}
1010
1011impl<'agent> QueryBuilder<'agent> {
1012    /// Creates a new query builder with an agent for a particular canister method.
1013    pub fn new(agent: &'agent Agent, canister_id: Principal, method_name: String) -> Self {
1014        Self {
1015            agent,
1016            effective_canister_id: canister_id,
1017            canister_id,
1018            method_name,
1019            arg: vec![],
1020            ingress_expiry_datetime: None,
1021        }
1022    }
1023
1024    /// Sets the [effective canister ID](https://smartcontracts.org/docs/interface-spec/index.html#http-effective-canister-id) of the destination.
1025    pub fn with_effective_canister_id(&mut self, canister_id: Principal) -> &mut Self {
1026        self.effective_canister_id = canister_id;
1027        self
1028    }
1029
1030    /// Sets the argument blob to pass to the canister. For most canisters this should be a Candid-serialized tuple.
1031    pub fn with_arg<A: AsRef<[u8]>>(&mut self, arg: A) -> &mut Self {
1032        self.arg = arg.as_ref().to_vec();
1033        self
1034    }
1035
1036    /// Takes a SystemTime converts it to a Duration by calling
1037    /// duration_since(UNIX_EPOCH) to learn about where in time this SystemTime lies.
1038    /// The Duration is converted to nanoseconds and stored in ingress_expiry_datetime
1039    pub fn expire_at(&mut self, time: std::time::SystemTime) -> &mut Self {
1040        self.ingress_expiry_datetime = Some(
1041            time.duration_since(std::time::UNIX_EPOCH)
1042                .expect("Time wrapped around")
1043                .as_nanos() as u64,
1044        );
1045        self
1046    }
1047
1048    /// Takes a Duration (i.e. 30 sec/5 min 30 sec/1 h 30 min, etc.) and adds it to the
1049    /// Duration of the current SystemTime since the UNIX_EPOCH
1050    /// Subtracts a permitted drift from the sum to account for using system time and not block time.
1051    /// Converts the difference to nanoseconds and stores in ingress_expiry_datetime
1052    pub fn expire_after(&mut self, duration: std::time::Duration) -> &mut Self {
1053        let permitted_drift = Duration::from_secs(60);
1054        self.ingress_expiry_datetime = Some(
1055            (duration
1056                .as_nanos()
1057                .saturating_add(
1058                    std::time::SystemTime::now()
1059                        .duration_since(std::time::UNIX_EPOCH)
1060                        .expect("Time wrapped around")
1061                        .as_nanos(),
1062                )
1063                .saturating_sub(permitted_drift.as_nanos())) as u64,
1064        );
1065        self
1066    }
1067
1068    /// Make a query call. This will return a byte vector.
1069    pub async fn call(&self) -> Result<Vec<u8>, AgentError> {
1070        self.agent
1071            .query_raw(
1072                &self.canister_id,
1073                self.effective_canister_id,
1074                self.method_name.as_str(),
1075                self.arg.as_slice(),
1076                self.ingress_expiry_datetime,
1077            )
1078            .await
1079    }
1080
1081    /// Sign a query call. This will return a [`signed::SignedQuery`]
1082    /// which contains all fields of the query and the signed query in CBOR encoding
1083    pub fn sign(&self) -> Result<signed::SignedQuery, AgentError> {
1084        let request = self.agent.query_content(
1085            &self.canister_id,
1086            &self.method_name,
1087            &self.arg,
1088            self.ingress_expiry_datetime,
1089        )?;
1090
1091        let signed_query = sign_request(&request, self.agent.identity.clone())?;
1092        match request {
1093            QueryContent::QueryRequest {
1094                ingress_expiry,
1095                sender,
1096                canister_id,
1097                method_name,
1098                arg,
1099            } => Ok(signed::SignedQuery {
1100                ingress_expiry,
1101                sender,
1102                canister_id,
1103                method_name,
1104                arg,
1105                effective_canister_id: self.effective_canister_id,
1106                signed_query,
1107            }),
1108        }
1109    }
1110}
1111
1112/// An in-flight canister update call. Useful primarily as a `Future`.
1113pub struct UpdateCall<'agent> {
1114    agent: &'agent Agent,
1115    request_id: AgentFuture<'agent, RequestId>,
1116    effective_canister_id: Principal,
1117}
1118
1119impl fmt::Debug for UpdateCall<'_> {
1120    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1121        f.debug_struct("UpdateCall")
1122            .field("agent", &self.agent)
1123            .field("effective_canister_id", &self.effective_canister_id)
1124            .finish_non_exhaustive()
1125    }
1126}
1127
1128impl Future for UpdateCall<'_> {
1129    type Output = Result<RequestId, AgentError>;
1130    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
1131        self.request_id.as_mut().poll(cx)
1132    }
1133}
1134impl<'a> UpdateCall<'a> {
1135    async fn and_wait(self) -> Result<Vec<u8>, AgentError> {
1136        let request_id = self.request_id.await?;
1137        self.agent
1138            .wait(request_id, self.effective_canister_id)
1139            .await
1140    }
1141}
1142/// An Update Request Builder.
1143///
1144/// This makes it easier to do update calls without actually passing all arguments or specifying
1145/// if you want to wait or not.
1146#[derive(Debug)]
1147pub struct UpdateBuilder<'agent> {
1148    agent: &'agent Agent,
1149    /// The [effective canister ID](https://smartcontracts.org/docs/interface-spec/index.html#http-effective-canister-id) of the destination.
1150    pub effective_canister_id: Principal,
1151    /// The principal ID of the canister being called.
1152    pub canister_id: Principal,
1153    /// The name of the canister method being called.
1154    pub method_name: String,
1155    /// The argument blob to be passed to the method.
1156    pub arg: Vec<u8>,
1157    /// The Unix timestamp that the request will expire at.
1158    pub ingress_expiry_datetime: Option<u64>,
1159}
1160
1161impl<'agent> UpdateBuilder<'agent> {
1162    /// Creates a new query builder with an agent for a particular canister method.
1163    pub fn new(agent: &'agent Agent, canister_id: Principal, method_name: String) -> Self {
1164        Self {
1165            agent,
1166            effective_canister_id: canister_id,
1167            canister_id,
1168            method_name,
1169            arg: vec![],
1170            ingress_expiry_datetime: None,
1171        }
1172    }
1173
1174    /// Sets the [effective canister ID](https://smartcontracts.org/docs/interface-spec/index.html#http-effective-canister-id) of the destination.
1175    pub fn with_effective_canister_id(&mut self, canister_id: Principal) -> &mut Self {
1176        self.effective_canister_id = canister_id;
1177        self
1178    }
1179
1180    /// Sets the argument blob to pass to the canister. For most canisters this should be a Candid-serialized tuple.
1181    pub fn with_arg<A: AsRef<[u8]>>(&mut self, arg: A) -> &mut Self {
1182        self.arg = arg.as_ref().to_vec();
1183        self
1184    }
1185
1186    /// Takes a SystemTime converts it to a Duration by calling
1187    /// duration_since(UNIX_EPOCH) to learn about where in time this SystemTime lies.
1188    /// The Duration is converted to nanoseconds and stored in ingress_expiry_datetime
1189    pub fn expire_at(&mut self, time: std::time::SystemTime) -> &mut Self {
1190        self.ingress_expiry_datetime = Some(
1191            time.duration_since(std::time::UNIX_EPOCH)
1192                .expect("Time wrapped around")
1193                .as_nanos() as u64,
1194        );
1195        self
1196    }
1197
1198    /// Takes a Duration (i.e. 30 sec/5 min 30 sec/1 h 30 min, etc.) and adds it to the
1199    /// Duration of the current SystemTime since the UNIX_EPOCH
1200    /// Subtracts a permitted drift from the sum to account for using system time and not block time.
1201    /// Converts the difference to nanoseconds and stores in ingress_expiry_datetime
1202    pub fn expire_after(&mut self, duration: std::time::Duration) -> &mut Self {
1203        let permitted_drift = Duration::from_secs(60);
1204        self.ingress_expiry_datetime = Some(
1205            (duration
1206                .as_nanos()
1207                .saturating_add(
1208                    std::time::SystemTime::now()
1209                        .duration_since(std::time::UNIX_EPOCH)
1210                        .expect("Time wrapped around")
1211                        .as_nanos(),
1212                )
1213                .saturating_sub(permitted_drift.as_nanos())) as u64,
1214        );
1215        self
1216    }
1217
1218    /// Make an update call. This will call request_status on the RequestId in a loop and return
1219    /// the response as a byte vector.
1220    pub async fn call_and_wait(&self) -> Result<Vec<u8>, AgentError> {
1221        self.call().and_wait().await
1222    }
1223
1224    /// Make an update call. This will return a RequestId.
1225    /// The RequestId should then be used for request_status (most likely in a loop).
1226    pub fn call(&self) -> UpdateCall {
1227        let request_id_future = self.agent.update_raw(
1228            &self.canister_id,
1229            self.effective_canister_id,
1230            self.method_name.as_str(),
1231            self.arg.as_slice(),
1232            self.ingress_expiry_datetime,
1233        );
1234        UpdateCall {
1235            agent: self.agent,
1236            request_id: Box::pin(request_id_future),
1237            effective_canister_id: self.effective_canister_id,
1238        }
1239    }
1240
1241    /// Sign a update call. This will return a [`signed::SignedUpdate`]
1242    /// which contains all fields of the update and the signed update in CBOR encoding
1243    pub fn sign(&self) -> Result<signed::SignedUpdate, AgentError> {
1244        let request = self.agent.update_content(
1245            &self.canister_id,
1246            &self.method_name,
1247            &self.arg,
1248            self.ingress_expiry_datetime,
1249        )?;
1250        let signed_update = sign_request(&request, self.agent.identity.clone())?;
1251        let request_id = to_request_id(&request)?;
1252        match request {
1253            CallRequestContent::CallRequest {
1254                nonce,
1255                ingress_expiry,
1256                sender,
1257                canister_id,
1258                method_name,
1259                arg,
1260            } => Ok(signed::SignedUpdate {
1261                nonce,
1262                ingress_expiry,
1263                sender,
1264                canister_id,
1265                method_name,
1266                arg,
1267                effective_canister_id: self.effective_canister_id,
1268                signed_update,
1269                request_id,
1270            }),
1271        }
1272    }
1273}