Skip to main content

ic_testkit/pocket_ic/
nonblocking.rs

1// Copyright DFINITY Foundation. Licensed under Apache-2.0.
2use crate::pocket_ic::common::rest::{
3    AutoProgressConfig, BlobCompression, BlobId, CanisterHttpRequest, CreateHttpGatewayResponse,
4    CreateInstanceResponse, ExtendedSubnetConfigSet, HttpGatewayBackend, HttpGatewayConfig,
5    HttpGatewayInfo, HttpsConfig, IcpConfig, IcpFeatures, InitialTime, InstanceConfig,
6    InstanceHttpGatewayConfig, InstanceId, MockCanisterHttpResponse,
7    MockFlexibleCanisterHttpResponse, RawAddCycles, RawCanisterCall, RawCanisterHttpRequest,
8    RawCanisterId, RawCanisterResult, RawCanisterSnapshotDownload, RawCanisterSnapshotId,
9    RawCanisterSnapshotUpload, RawCycles, RawEffectivePrincipal, RawIngressStatusArgs,
10    RawMessageId, RawMockCanisterHttpResponse, RawMockFlexibleCanisterHttpResponse, RawPrincipalId,
11    RawSenderInfo, RawSetStableMemory, RawStableMemory, RawSubnetId, RawTickConfigs, RawTime,
12    RawVerifyCanisterSigArg, SubnetId, Topology,
13};
14#[cfg(windows)]
15use crate::pocket_ic::wsl_path;
16use crate::pocket_ic::{ClientError, Operation, RequestContext, http::Exchange};
17use crate::pocket_ic::{
18    CreateCanisterParams, CreateCanisterPlacement, IngressStatusResult, PocketIcBuilder,
19    PocketIcState, RejectResponse, TickConfigs, Time, copy_dir,
20};
21use backon::{BackoffBuilder, ExponentialBuilder};
22use candid::{
23    Principal, decode_args, encode_args,
24    utils::{ArgumentDecoder, ArgumentEncoder},
25};
26use ic_management_canister_types::{
27    CanisterId, CanisterIdRecord, CanisterInstallMode, CanisterLogRecord, CanisterSettings,
28    CanisterStatusResult, ChunkHash, DeleteCanisterSnapshotArgs, FetchCanisterLogsResult,
29    InstallChunkedCodeArgs, InstallCodeArgs, LoadCanisterSnapshotArgs,
30    ProvisionalCreateCanisterWithCyclesArgs, Snapshot, StoredChunksResult,
31    TakeCanisterSnapshotArgs, UpdateSettingsArgs, UpgradeFlags, UploadChunkArgs, UploadChunkResult,
32    WasmMemoryPersistence,
33};
34use ic_platform::certification::cbor::decode_certificate;
35use ic_platform::certification::{Certificate, Label, LookupResult};
36use ic_platform::ic_protocol::Envelope;
37use ic_platform::ic_protocol::EnvelopeContent::ReadState;
38use ic_platform::ic_protocol::{
39    ReadStateResponse, SubnetMetrics, decode_read_state_response, decode_subnet_metrics,
40    encode_envelope,
41};
42use reqwest::{StatusCode, Url};
43use serde::{Serialize, de::DeserializeOwned};
44use sha2::{Digest, Sha256};
45use slog::Level;
46use std::fs::read_dir;
47use std::future::Future;
48use std::net::{IpAddr, SocketAddr};
49use std::path::PathBuf;
50use std::time::Duration;
51use tracing::{debug, instrument};
52use tracing_appender::non_blocking::WorkerGuard;
53use tracing_subscriber::EnvFilter;
54
55// wait time between polling requests
56
57mod snapshot;
58
59const LOG_DIR_PATH_ENV_NAME: &str = "POCKET_IC_LOG_DIR";
60const LOG_DIR_LEVELS_ENV_NAME: &str = "POCKET_IC_LOG_DIR_LEVELS";
61
62const LOCALHOST: &str = "localhost";
63
64// The minimum joint size of a canister's WASM
65// and its initial argument blob for which
66// we install the canister WASM as a sequence of chunks.
67// The size is chosen to be smaller than the maximum
68// ingress message size of 2 MiB (2,097,152 B)
69// on application subnet (which have the tighest
70// ingress message size limit)
71// to account for a few additional constant-size fields
72// in the management canister payload to install code
73// and the overhead of candid encoding.
74const INSTALL_CHUNKED_CODE_THRESHOLD: usize = 2_000_000; // 2 MB
75
76// The maximum size of one WASM chunk when installing a canister WASM
77// as a sequence of chunks. This constant is specified
78// in the IC protocol.
79const INSTALL_CODE_CHUNK_SIZE: usize = 1 << 20; // 1 MiB
80
81enum HttpMethod {
82    Get,
83    Post,
84}
85
86/// Main entry point for interacting with PocketIC.
87pub struct PocketIc {
88    /// The unique ID of this PocketIC instance.
89    pub instance_id: InstanceId,
90    // how long a get/post request may retry or poll
91    max_request_time_ms: Option<u64>,
92    http_gateway: Option<HttpGatewayInfo>,
93    server_url: Url,
94    reqwest_client: reqwest::Client,
95    // the instance should only be deleted when dropping this handle if this handle owns the instance
96    owns_instance: bool,
97    state_dir: Option<PocketIcState>,
98    _log_guard: Option<WorkerGuard>,
99}
100
101impl PocketIc {
102    /// Creates a new PocketIC instance with a single application subnet on the server.
103    /// The caller supplies an already admitted server endpoint.
104    pub async fn new(server_url: Url) -> Result<Self, ClientError> {
105        PocketIcBuilder::new()
106            .with_server_url(server_url)
107            .with_application_subnet()
108            .build()
109            .await
110    }
111
112    /// Creates a PocketIC handle to an existing instance on a running server.
113    /// Note that this handle does not extend the lifetime of the existing instance,
114    /// i.e., the existing instance is deleted and this handle stops working
115    /// when the PocketIC handle that created the existing instance is dropped.
116    pub fn new_from_existing_instance(
117        server_url: Url,
118        instance_id: InstanceId,
119        max_request_time_ms: Option<u64>,
120    ) -> Self {
121        let test_driver_pid = std::process::id();
122        let log_guard = setup_tracing(test_driver_pid);
123
124        let reqwest_client = reqwest::Client::new();
125        debug!("instance_id={} Reusing existing instance.", instance_id);
126
127        Self {
128            instance_id,
129            max_request_time_ms,
130            http_gateway: None,
131            server_url,
132            reqwest_client,
133            owns_instance: false,
134            state_dir: None,
135            _log_guard: log_guard,
136        }
137    }
138
139    #[expect(
140        clippy::too_many_arguments,
141        reason = "retained upstream client method signature"
142    )]
143    pub(crate) async fn from_components(
144        subnet_config_set: impl Into<ExtendedSubnetConfigSet>,
145        server_url: Url,
146        max_request_time_ms: Option<u64>,
147        read_only_state_dir: Option<PathBuf>,
148        mut state_dir: Option<PocketIcState>,
149        icp_config: IcpConfig,
150        log_level: Option<Level>,
151        bitcoind_addr: Option<Vec<SocketAddr>>,
152        dogecoind_addr: Option<Vec<SocketAddr>>,
153        icp_features: IcpFeatures,
154        initial_time: Option<InitialTime>,
155        http_gateway_config: Option<InstanceHttpGatewayConfig>,
156        mainnet_nns_subnet_id: Option<bool>,
157        disable_ingress_validation: Option<bool>,
158    ) -> Result<Self, ClientError> {
159        let subnet_config_set: ExtendedSubnetConfigSet = subnet_config_set.into();
160
161        // copy the read-only state dir to the state dir
162        // (creating an empty temp dir to serve as the state dir if no state dir is provided)
163        if let Some(read_only_state_dir) = read_only_state_dir {
164            if let Some(ref state_dir) = state_dir {
165                let mut state_dir_contents = read_dir(state_dir.state_dir())?;
166                if state_dir_contents.next().is_some() {
167                    return Err(ClientError::Protocol(
168                        "PocketIC instance state must be empty if a read-only state is mounted."
169                            .into(),
170                    ));
171                }
172            } else {
173                state_dir = Some(PocketIcState::new());
174            };
175            copy_dir(
176                read_only_state_dir,
177                state_dir
178                    .as_ref()
179                    .map(|state_dir| state_dir.state_dir())
180                    .unwrap(),
181            )?;
182        };
183
184        let instance_config = InstanceConfig {
185            subnet_config_set,
186            http_gateway_config,
187            #[cfg(not(windows))]
188            state_dir: state_dir.as_ref().map(|state_dir| state_dir.state_dir()),
189            #[cfg(windows)]
190            state_dir: state_dir
191                .as_ref()
192                .map(|state_dir| wsl_path(&state_dir.state_dir(), "state directory").into()),
193            icp_config: Some(icp_config),
194            log_level: log_level.map(|l| l.to_string()),
195            bitcoind_addr,
196            dogecoind_addr,
197            icp_features: Some(icp_features),
198            incomplete_state: None,
199            initial_time,
200            mainnet_nns_subnet_id,
201            disable_ingress_validation,
202        };
203
204        let test_driver_pid = std::process::id();
205        let log_guard = setup_tracing(test_driver_pid);
206
207        let reqwest_client = reqwest::Client::new();
208        let url = server_url.join("instances").unwrap();
209        let mut exchange = Exchange::new(&url, max_request_time_ms);
210        let response: CreateInstanceResponse = exchange
211            .json(
212                &reqwest_client,
213                reqwest_client.post(url.clone()).json(&instance_config),
214            )
215            .await?;
216        let (instance_id, http_gateway_info) = match response {
217            CreateInstanceResponse::Created {
218                instance_id,
219                http_gateway_info,
220                ..
221            } => (instance_id, http_gateway_info),
222            CreateInstanceResponse::Error { message } => {
223                return Err(ClientError::Server {
224                    context: Box::new(exchange.context.clone()),
225                    status: exchange.context.status.unwrap_or(StatusCode::OK),
226                    message,
227                });
228            }
229        };
230        debug!("instance_id={} New instance created.", instance_id);
231
232        Ok(Self {
233            instance_id,
234            max_request_time_ms,
235            http_gateway: http_gateway_info,
236            server_url,
237            reqwest_client,
238            owns_instance: true,
239            state_dir,
240            _log_guard: log_guard,
241        })
242    }
243
244    pub async fn drop_and_take_state(&mut self) -> Result<Option<PocketIcState>, ClientError> {
245        self.do_drop().await?;
246        Ok(self.state_dir.take())
247    }
248
249    /// Returns the URL of the PocketIC server on which this PocketIC instance is running.
250    pub fn get_server_url(&self) -> Url {
251        self.server_url.clone()
252    }
253
254    /// Returns the topology of the different subnets of this PocketIC instance.
255    pub async fn topology(&self) -> Result<Topology, ClientError> {
256        let endpoint = "read/topology";
257        self.get(endpoint).await
258    }
259
260    /// Upload and store a binary blob to the PocketIC server.
261    #[instrument(ret, skip(self, blob), fields(instance_id=self.instance_id, blob_len = %blob.len(), compression = ?compression))]
262    pub async fn upload_blob(
263        &self,
264        blob: Vec<u8>,
265        compression: BlobCompression,
266    ) -> Result<BlobId, ClientError> {
267        let url = self.server_url.join("blobstore/").unwrap();
268        let mut request = self.reqwest_client.post(url.clone()).body(blob);
269        if let BlobCompression::Gzip = compression {
270            request = request.header(reqwest::header::CONTENT_ENCODING, "gzip");
271        }
272        let mut exchange = Exchange::new(&url, self.max_request_time_ms);
273        let (status, bytes) = exchange.bytes(&self.reqwest_client, request).await?;
274        if !status.is_success() {
275            return Err(exchange.server_error(status, &bytes));
276        }
277        let hash = hex::decode(bytes).map_err(|error| ClientError::Protocol(error.to_string()))?;
278        Ok(BlobId(hash))
279    }
280
281    /// Set stable memory of a canister. Optional GZIP compression can be used for reduced
282    /// data traffic.
283    #[instrument(skip(self, data), fields(instance_id=self.instance_id, canister_id = %canister_id.to_string(), data_len = %data.len(), compression = ?compression))]
284    pub async fn set_stable_memory(
285        &self,
286        canister_id: CanisterId,
287        data: Vec<u8>,
288        compression: BlobCompression,
289    ) -> Result<(), ClientError> {
290        let blob_id = self.upload_blob(data, compression).await?;
291        let endpoint = "update/set_stable_memory";
292        self.post::<(), _>(
293            endpoint,
294            RawSetStableMemory {
295                canister_id: canister_id.as_slice().to_vec(),
296                blob_id,
297            },
298        )
299        .await?;
300
301        Ok(())
302    }
303
304    /// Get stable memory of a canister.
305    #[instrument(skip(self), fields(instance_id=self.instance_id, canister_id = %canister_id.to_string()))]
306    pub async fn get_stable_memory(&self, canister_id: CanisterId) -> Result<Vec<u8>, ClientError> {
307        let endpoint = "read/get_stable_memory";
308        let RawStableMemory { blob } = self
309            .post(
310                endpoint,
311                RawCanisterId {
312                    canister_id: canister_id.as_slice().to_vec(),
313                },
314            )
315            .await?;
316        Ok(blob)
317    }
318
319    /// List all instances and their status.
320    #[instrument(ret)]
321    pub async fn list_instances(server_url: Url) -> Result<Vec<String>, ClientError> {
322        let url = server_url.join("instances").unwrap();
323        let client = reqwest::Client::new();
324        Exchange::new(&url, None)
325            .json(&client, client.get(url.clone()))
326            .await
327    }
328
329    /// Verify a canister signature.
330    #[instrument(skip_all, fields(instance_id=self.instance_id))]
331    pub async fn verify_canister_signature(
332        &self,
333        msg: Vec<u8>,
334        sig: Vec<u8>,
335        pubkey: Vec<u8>,
336        root_pubkey: Vec<u8>,
337    ) -> Result<(), ClientError> {
338        let url = self.server_url.join("verify_signature").unwrap();
339        let result: Result<(), String> = Exchange::new(&url, self.max_request_time_ms)
340            .json(
341                &self.reqwest_client,
342                self.reqwest_client
343                    .post(url.clone())
344                    .json(&RawVerifyCanisterSigArg {
345                        msg,
346                        sig,
347                        pubkey,
348                        root_pubkey,
349                    }),
350            )
351            .await?;
352        result.map_err(ClientError::Protocol)
353    }
354
355    /// Make the IC produce and progress by one block.
356    /// Note that multiple ticks might be necessary to observe
357    /// an expected effect, e.g., if the effect depends on
358    /// inter-canister calls or heartbeats.
359    #[instrument(skip(self), fields(instance_id=self.instance_id))]
360    pub async fn tick(&self) -> Result<(), ClientError> {
361        self.tick_with_configs(TickConfigs::default()).await?;
362
363        Ok(())
364    }
365
366    /// Make the IC produce and progress by one block with custom
367    /// configs for the round.
368    #[instrument(skip(self), fields(instance_id=self.instance_id))]
369    pub async fn tick_with_configs(&self, configs: TickConfigs) -> Result<(), ClientError> {
370        let endpoint = "update/tick";
371        self.post::<(), RawTickConfigs>(endpoint, configs.into())
372            .await?;
373
374        Ok(())
375    }
376
377    /// Configures the IC to make progress automatically,
378    /// i.e., periodically update the time of the IC
379    /// to the real time and execute rounds on the subnets.
380    /// Only returns after the certified time of the IC
381    /// has been updated for the first time.
382    /// Returns the URL at which `/api` requests
383    /// for this instance can be made.
384    #[instrument(skip(self), fields(instance_id=self.instance_id))]
385    pub async fn auto_progress(&self) -> Result<Url, ClientError> {
386        let endpoint = "auto_progress";
387        let auto_progress_config = AutoProgressConfig {
388            artificial_delay_ms: None,
389        };
390        self.post::<(), _>(endpoint, auto_progress_config).await?;
391        Ok(self.instance_url())
392    }
393
394    /// Returns whether automatic progress is enabled on the PocketIC instance.
395    #[instrument(skip(self), fields(instance_id=self.instance_id))]
396    pub async fn auto_progress_enabled(&self) -> Result<bool, ClientError> {
397        self.get("auto_progress").await
398    }
399
400    pub(crate) fn instance_url(&self) -> Url {
401        self.server_url
402            .join("/instances/")
403            .unwrap()
404            .join(&format!("{}/", self.instance_id))
405            .unwrap()
406    }
407
408    /// Stops automatic progress (see `auto_progress`) on the IC.
409    #[instrument(skip(self), fields(instance_id=self.instance_id))]
410    pub async fn stop_progress(&self) -> Result<(), ClientError> {
411        let endpoint = "stop_progress";
412        self.post::<(), _>(endpoint, "").await?;
413
414        Ok(())
415    }
416
417    /// Returns the URL at which `/api` requests
418    /// for this instance can be made if the HTTP
419    /// gateway has been started.
420    pub fn url(&self) -> Option<Url> {
421        self.http_gateway
422            .as_ref()
423            .map(|res| Url::parse(&format!("http://{}:{}/", LOCALHOST, res.port)).unwrap())
424    }
425
426    /// Creates an HTTP gateway for this PocketIC instance binding to `127.0.0.1`
427    /// and an optionally specified port (defaults to choosing an arbitrary unassigned port);
428    /// listening on `localhost`;
429    /// and configures the PocketIC instance to make progress automatically, i.e.,
430    /// periodically update the time of the PocketIC instance to the real time
431    /// and process messages on the PocketIC instance.
432    /// Only returns after the certified time of the PocketIC instance
433    /// has been updated for the first time.
434    /// Returns the URL at which `/api` requests
435    /// for this instance can be made.
436    #[instrument(skip(self), fields(instance_id=self.instance_id))]
437    pub async fn make_live(&mut self, listen_at: Option<u16>) -> Result<Url, ClientError> {
438        self.make_live_with_params(None, listen_at, None, None)
439            .await
440    }
441
442    /// Creates an HTTP gateway for this PocketIC instance binding
443    /// to an optionally specified IP address (defaults to `127.0.0.1`)
444    /// and port (defaults to choosing an arbitrary unassigned port);
445    /// listening on optionally specified domains (default to `localhost`);
446    /// and using an optionally specified TLS certificate (if provided, an HTTPS gateway is created)
447    /// and configures the PocketIC instance to make progress automatically, i.e.,
448    /// periodically update the time of the PocketIC instance to the real time
449    /// and process messages on the PocketIC instance.
450    /// Only returns after the certified time of the PocketIC instance
451    /// has been updated for the first time.
452    /// Returns the URL at which `/api` requests
453    /// for this instance can be made.
454    #[instrument(skip(self), fields(instance_id=self.instance_id))]
455    pub async fn make_live_with_params(
456        &mut self,
457        ip_addr: Option<IpAddr>,
458        listen_at: Option<u16>,
459        domains: Option<Vec<String>>,
460        https_config: Option<HttpsConfig>,
461    ) -> Result<Url, ClientError> {
462        if let Some(url) = self.url() {
463            return Ok(url);
464        }
465        if !self.auto_progress_enabled().await? {
466            self.auto_progress().await?;
467        }
468        self.start_http_gateway(
469            ip_addr.map(|ip_addr| ip_addr.to_string()),
470            listen_at,
471            domains,
472            https_config,
473        )
474        .await
475    }
476
477    async fn start_http_gateway(
478        &mut self,
479        ip_addr: Option<String>,
480        port: Option<u16>,
481        domains: Option<Vec<String>>,
482        https_config: Option<HttpsConfig>,
483    ) -> Result<Url, ClientError> {
484        let endpoint = self.server_url.join("http_gateway").unwrap();
485        let http_gateway_config = HttpGatewayConfig {
486            ip_addr,
487            port,
488            forward_to: HttpGatewayBackend::PocketIcInstance(self.instance_id),
489            domains: domains.clone(),
490            https_config: https_config.clone(),
491            domain_custom_provider_local_file: None,
492        };
493        let mut exchange = Exchange::new(&endpoint, self.max_request_time_ms);
494        let res: CreateHttpGatewayResponse = exchange
495            .json(
496                &self.reqwest_client,
497                self.reqwest_client
498                    .post(endpoint.clone())
499                    .json(&http_gateway_config),
500            )
501            .await?;
502        Ok(match res {
503            CreateHttpGatewayResponse::Created(info) => {
504                let port = info.port;
505                self.http_gateway = Some(info);
506                let proto = if https_config.is_some() {
507                    "https"
508                } else {
509                    "http"
510                };
511                Url::parse(&format!(
512                    "{}://{}:{}/",
513                    proto,
514                    domains
515                        .unwrap_or_default()
516                        .into_iter()
517                        .next()
518                        .unwrap_or(LOCALHOST.to_string()),
519                    port
520                ))
521                .unwrap()
522            }
523            CreateHttpGatewayResponse::Error { message } => {
524                return Err(ClientError::Server {
525                    context: Box::new(exchange.context.clone()),
526                    status: exchange.context.status.unwrap_or(StatusCode::OK),
527                    message,
528                });
529            }
530        })
531    }
532
533    async fn stop_http_gateway(&mut self) -> Result<(), ClientError> {
534        if let Some(info) = &self.http_gateway {
535            let url = self
536                .server_url
537                .join(&format!("http_gateway/{}/stop", info.instance_id))
538                .unwrap();
539            Exchange::new(&url, self.max_request_time_ms)
540                .json::<()>(&self.reqwest_client, self.reqwest_client.post(url.clone()))
541                .await?;
542            self.http_gateway = None;
543        }
544        Ok(())
545    }
546
547    /// Stops auto progress (automatic time updates and round executions)
548    /// and the HTTP gateway for this IC instance.
549    #[instrument(skip(self), fields(instance_id=self.instance_id))]
550    pub async fn stop_live(&mut self) -> Result<(), ClientError> {
551        self.stop_http_gateway().await?;
552        self.stop_progress().await?;
553
554        Ok(())
555    }
556
557    /// Get the root key of this IC instance. Returns `None` if the IC has no NNS subnet.
558    #[instrument(skip(self), fields(instance_id=self.instance_id))]
559    pub async fn root_key(&self) -> Result<Option<Vec<u8>>, ClientError> {
560        let Some(subnet_id) = self.topology().await?.get_nns() else {
561            return Ok(None);
562        };
563        let subnet_id: RawSubnetId = subnet_id.into();
564        let endpoint = "read/pub_key";
565        let res = self.post::<Vec<u8>, _>(endpoint, subnet_id).await?;
566        Ok(Some(res))
567    }
568
569    /// Get the current time of the IC.
570    #[instrument(ret, skip(self), fields(instance_id=self.instance_id))]
571    pub async fn get_time(&self) -> Result<Time, ClientError> {
572        let endpoint = "read/get_time";
573        let result: RawTime = self.get(endpoint).await?;
574        Ok(Time::from_nanos_since_unix_epoch(result.nanos_since_epoch))
575    }
576
577    /// Set the current time of the IC, on all subnets.
578    #[instrument(skip(self), fields(instance_id=self.instance_id, time = ?time))]
579    pub async fn set_time(&self, time: Time) -> Result<(), ClientError> {
580        let endpoint = "update/set_time";
581        self.post::<(), _>(
582            endpoint,
583            RawTime {
584                nanos_since_epoch: time.as_nanos_since_unix_epoch(),
585            },
586        )
587        .await?;
588
589        Ok(())
590    }
591
592    /// Set the current certified time of the IC, on all subnets.
593    #[instrument(skip(self), fields(instance_id=self.instance_id, time = ?time))]
594    pub async fn set_certified_time(&self, time: Time) -> Result<(), ClientError> {
595        let endpoint = "update/set_certified_time";
596        self.post::<(), _>(
597            endpoint,
598            RawTime {
599                nanos_since_epoch: time.as_nanos_since_unix_epoch(),
600            },
601        )
602        .await?;
603
604        Ok(())
605    }
606
607    /// Advance the time on the IC on all subnets by some nanoseconds.
608    #[instrument(skip(self), fields(instance_id=self.instance_id, duration = ?duration))]
609    pub async fn advance_time(&self, duration: Duration) -> Result<(), ClientError> {
610        let now = self.get_time().await?;
611        self.set_time(now + duration).await?;
612
613        Ok(())
614    }
615
616    /// Get the controllers of a canister.
617    /// Returns a server error if the canister does not exist.
618    #[instrument(ret, skip(self), fields(instance_id=self.instance_id, canister_id = %canister_id.to_string()))]
619    pub async fn get_controllers(
620        &self,
621        canister_id: CanisterId,
622    ) -> Result<Vec<Principal>, ClientError> {
623        let endpoint = "read/get_controllers";
624        let result: Vec<RawPrincipalId> = self
625            .post(
626                endpoint,
627                RawCanisterId {
628                    canister_id: canister_id.as_slice().to_vec(),
629                },
630            )
631            .await?;
632        Ok(result.into_iter().map(|p| p.into()).collect())
633    }
634
635    /// Get the current cycles balance of a canister.
636    #[instrument(ret, skip(self), fields(instance_id=self.instance_id, canister_id = %canister_id.to_string()))]
637    pub async fn cycle_balance(&self, canister_id: CanisterId) -> Result<u128, ClientError> {
638        let endpoint = "read/get_cycles";
639        let result: RawCycles = self
640            .post(
641                endpoint,
642                RawCanisterId {
643                    canister_id: canister_id.as_slice().to_vec(),
644                },
645            )
646            .await?;
647        Ok(result.cycles)
648    }
649
650    /// Add cycles to a canister. Returns the new balance.
651    #[instrument(ret, skip(self), fields(instance_id=self.instance_id, canister_id = %canister_id.to_string(), amount = %amount))]
652    pub async fn add_cycles(
653        &self,
654        canister_id: CanisterId,
655        amount: u128,
656    ) -> Result<u128, ClientError> {
657        let endpoint = "update/add_cycles";
658        let result: RawCycles = self
659            .post(
660                endpoint,
661                RawAddCycles {
662                    canister_id: canister_id.as_slice().to_vec(),
663                    amount,
664                },
665            )
666            .await?;
667        Ok(result.cycles)
668    }
669
670    /// Submit an update call (without executing it immediately).
671    pub async fn submit_call(
672        &self,
673        canister_id: CanisterId,
674        sender: Principal,
675        method: &str,
676        payload: Vec<u8>,
677    ) -> Result<RawMessageId, ClientError> {
678        self.submit_call_with_effective_principal(
679            canister_id,
680            RawEffectivePrincipal::CanisterId(canister_id.as_slice().to_vec()),
681            sender,
682            method,
683            payload,
684        )
685        .await
686    }
687
688    async fn submit_call_with_effective_principal_and_sender_info_inner(
689        &self,
690        canister_id: CanisterId,
691        effective_principal: RawEffectivePrincipal,
692        sender: Principal,
693        method: &str,
694        payload: Vec<u8>,
695        sender_info: Option<RawSenderInfo>,
696    ) -> Result<RawMessageId, ClientError> {
697        let endpoint = "update/submit_ingress_message";
698        let raw_canister_call = RawCanisterCall {
699            sender: sender.as_slice().to_vec(),
700            canister_id: canister_id.as_slice().to_vec(),
701            method: method.to_string(),
702            payload,
703            effective_principal,
704            sender_info,
705        };
706        self.post::<Result<RawMessageId, RejectResponse>, _>(endpoint, raw_canister_call)
707            .await?
708            .map_err(ClientError::from)
709    }
710
711    /// Submit an update call with a provided effective principal (without executing it immediately).
712    pub async fn submit_call_with_effective_principal(
713        &self,
714        canister_id: CanisterId,
715        effective_principal: RawEffectivePrincipal,
716        sender: Principal,
717        method: &str,
718        payload: Vec<u8>,
719    ) -> Result<RawMessageId, ClientError> {
720        self.submit_call_with_effective_principal_and_sender_info_inner(
721            canister_id,
722            effective_principal,
723            sender,
724            method,
725            payload,
726            None,
727        )
728        .await
729    }
730
731    /// Submit an update call with a provided effective principal and sender info (without executing it immediately).
732    pub async fn submit_call_with_effective_principal_and_sender_info(
733        &self,
734        canister_id: CanisterId,
735        effective_principal: RawEffectivePrincipal,
736        sender: Principal,
737        method: &str,
738        payload: Vec<u8>,
739        sender_info: RawSenderInfo,
740    ) -> Result<RawMessageId, ClientError> {
741        self.submit_call_with_effective_principal_and_sender_info_inner(
742            canister_id,
743            effective_principal,
744            sender,
745            method,
746            payload,
747            Some(sender_info),
748        )
749        .await
750    }
751
752    /// Submit an update call with sender info (without executing it immediately).
753    pub async fn submit_call_with_sender_info(
754        &self,
755        canister_id: CanisterId,
756        sender: Principal,
757        method: &str,
758        payload: Vec<u8>,
759        sender_info: RawSenderInfo,
760    ) -> Result<RawMessageId, ClientError> {
761        self.submit_call_with_effective_principal_and_sender_info(
762            canister_id,
763            RawEffectivePrincipal::CanisterId(canister_id.as_slice().to_vec()),
764            sender,
765            method,
766            payload,
767            sender_info,
768        )
769        .await
770    }
771
772    /// Await an update call submitted previously by `submit_call` or `submit_call_with_effective_principal`.
773    pub async fn await_call(&self, message_id: RawMessageId) -> Result<Vec<u8>, ClientError> {
774        let endpoint = "update/await_ingress_message";
775        let result: RawCanisterResult = self
776            .post(endpoint, &message_id)
777            .await
778            .map_err(|error| error.with_ingress(message_id))?;
779        Result::<Vec<u8>, RejectResponse>::from(result).map_err(ClientError::from)
780    }
781
782    /// Fetch the status of an update call submitted previously by `submit_call` or `submit_call_with_effective_principal`.
783    /// Note that the status of the update call can only change if the PocketIC instance is in live mode
784    /// or a round has been executed due to a separate PocketIC library call, e.g., `PocketIc::tick()`.
785    pub async fn ingress_status(
786        &self,
787        raw_message_id: RawMessageId,
788    ) -> Result<Option<Result<Vec<u8>, RejectResponse>>, ClientError> {
789        let status = self.ingress_status_as_caller(raw_message_id, None).await?;
790        Ok(match status {
791            IngressStatusResult::NotAvailable => None,
792            IngressStatusResult::Success(status) => Some(status),
793            IngressStatusResult::Forbidden(err) => return Err(ClientError::Protocol(err)),
794        })
795    }
796
797    /// Fetch the status of an update call submitted previously by `submit_call` or `submit_call_with_effective_principal`.
798    /// Note that the status of the update call can only change if the PocketIC instance is in live mode
799    /// or a round has been executed due to a separate PocketIC library call, e.g., `PocketIc::tick()`.
800    /// If the status of the update call is known, but the update call was submitted by a different caller, then an error is returned.
801    pub async fn ingress_status_as(
802        &self,
803        raw_message_id: RawMessageId,
804        caller: Principal,
805    ) -> Result<IngressStatusResult, ClientError> {
806        self.ingress_status_as_caller(raw_message_id, Some(caller))
807            .await
808    }
809
810    async fn ingress_status_as_caller(
811        &self,
812        raw_message_id: RawMessageId,
813        caller: Option<Principal>,
814    ) -> Result<IngressStatusResult, ClientError> {
815        let args = RawIngressStatusArgs {
816            raw_message_id: raw_message_id.clone(),
817            raw_caller: caller.map(Into::into),
818        };
819        match self
820            .post::<Option<RawCanisterResult>, _>("read/ingress_status", args)
821            .await
822        {
823            Ok(None) => Ok(IngressStatusResult::NotAvailable),
824            Ok(Some(result)) => Ok(IngressStatusResult::Success(result.into())),
825            Err(ClientError::Server {
826                status: StatusCode::FORBIDDEN,
827                message,
828                ..
829            }) => Ok(IngressStatusResult::Forbidden(message)),
830            Err(error) => Err(error.with_ingress(raw_message_id)),
831        }
832    }
833
834    /// Await an update call submitted previously by `submit_call` or `submit_call_with_effective_principal`.
835    /// Note that the status of the update call can only change if the PocketIC instance is in live mode
836    /// or a round has been executed due to a separate PocketIC library call.
837    pub async fn await_call_no_ticks(
838        &self,
839        message_id: RawMessageId,
840    ) -> Result<Vec<u8>, ClientError> {
841        let mut retry_policy = ExponentialBuilder::new()
842            .with_min_delay(Duration::from_millis(10))
843            .with_max_delay(Duration::from_secs(1))
844            .with_factor(2.0)
845            .with_jitter()
846            .with_total_delay(Some(Duration::from_secs(15 * 60)))
847            .without_max_times()
848            .build();
849        Ok(loop {
850            if let Some(ingress_status) = self.ingress_status(message_id.clone()).await? {
851                break ingress_status.map_err(ClientError::from)?;
852            }
853            let delay = retry_policy.next().ok_or_else(|| ClientError::Timeout {
854                context: Box::new(RequestContext {
855                    url: self.instance_url().to_string(),
856                    operation: None,
857                    ingress: Some(message_id.clone()),
858                    may_have_executed: true,
859                    status: None,
860                }),
861            })?;
862            tokio::time::sleep(delay).await;
863        })
864    }
865
866    /// Execute an update call on a canister.
867    #[instrument(skip(self, payload), fields(instance_id=self.instance_id, canister_id = %canister_id.to_string(), sender = %sender.to_string(), method = %method, payload_len = %payload.len()))]
868    pub async fn update_call(
869        &self,
870        canister_id: CanisterId,
871        sender: Principal,
872        method: &str,
873        payload: Vec<u8>,
874    ) -> Result<Vec<u8>, ClientError> {
875        self.update_call_with_effective_principal(
876            canister_id,
877            RawEffectivePrincipal::CanisterId(canister_id.as_slice().to_vec()),
878            sender,
879            method,
880            payload,
881        )
882        .await
883    }
884
885    async fn update_call_with_effective_principal_and_sender_info_inner(
886        &self,
887        canister_id: CanisterId,
888        effective_principal: RawEffectivePrincipal,
889        sender: Principal,
890        method: &str,
891        payload: Vec<u8>,
892        sender_info: Option<RawSenderInfo>,
893    ) -> Result<Vec<u8>, ClientError> {
894        let message_id = self
895            .submit_call_with_effective_principal_and_sender_info_inner(
896                canister_id,
897                effective_principal,
898                sender,
899                method,
900                payload,
901                sender_info,
902            )
903            .await?;
904        self.await_call(message_id).await
905    }
906
907    /// Execute an update call with a provided effective principal and sender info on a canister.
908    pub async fn update_call_with_effective_principal_and_sender_info(
909        &self,
910        canister_id: CanisterId,
911        effective_principal: RawEffectivePrincipal,
912        sender: Principal,
913        method: &str,
914        payload: Vec<u8>,
915        sender_info: RawSenderInfo,
916    ) -> Result<Vec<u8>, ClientError> {
917        self.update_call_with_effective_principal_and_sender_info_inner(
918            canister_id,
919            effective_principal,
920            sender,
921            method,
922            payload,
923            Some(sender_info),
924        )
925        .await
926    }
927
928    /// Execute an update call with sender info on a canister.
929    pub async fn update_call_with_sender_info(
930        &self,
931        canister_id: CanisterId,
932        sender: Principal,
933        method: &str,
934        payload: Vec<u8>,
935        sender_info: RawSenderInfo,
936    ) -> Result<Vec<u8>, ClientError> {
937        self.update_call_with_effective_principal_and_sender_info(
938            canister_id,
939            RawEffectivePrincipal::CanisterId(canister_id.as_slice().to_vec()),
940            sender,
941            method,
942            payload,
943            sender_info,
944        )
945        .await
946    }
947
948    /// Execute a query call on a canister.
949    #[instrument(skip(self, payload), fields(instance_id=self.instance_id, canister_id = %canister_id.to_string(), sender = %sender.to_string(), method = %method, payload_len = %payload.len()))]
950    pub async fn query_call(
951        &self,
952        canister_id: CanisterId,
953        sender: Principal,
954        method: &str,
955        payload: Vec<u8>,
956    ) -> Result<Vec<u8>, ClientError> {
957        self.query_call_with_effective_principal(
958            canister_id,
959            RawEffectivePrincipal::CanisterId(canister_id.as_slice().to_vec()),
960            sender,
961            method,
962            payload,
963        )
964        .await
965    }
966
967    /// Execute a query call on a canister explicitly specifying an effective principal to route the request:
968    /// this API is useful for making generic query calls (including management canister query calls) without using dedicated functions from this library
969    /// (e.g., making generic query calls in dfx to a PocketIC instance).
970    #[instrument(skip(self, payload), fields(instance_id=self.instance_id, canister_id = %canister_id.to_string(), effective_principal = %effective_principal.to_string(), sender = %sender.to_string(), method = %method, payload_len = %payload.len()))]
971    pub async fn query_call_with_effective_principal(
972        &self,
973        canister_id: CanisterId,
974        effective_principal: RawEffectivePrincipal,
975        sender: Principal,
976        method: &str,
977        payload: Vec<u8>,
978    ) -> Result<Vec<u8>, ClientError> {
979        let endpoint = "read/query";
980        self.canister_call(
981            endpoint,
982            effective_principal,
983            canister_id,
984            sender,
985            method,
986            payload,
987            None,
988        )
989        .await
990    }
991
992    /// Execute a query call with a provided effective principal and sender info on a canister.
993    pub async fn query_call_with_effective_principal_and_sender_info(
994        &self,
995        canister_id: CanisterId,
996        effective_principal: RawEffectivePrincipal,
997        sender: Principal,
998        method: &str,
999        payload: Vec<u8>,
1000        sender_info: RawSenderInfo,
1001    ) -> Result<Vec<u8>, ClientError> {
1002        let endpoint = "read/query";
1003        self.canister_call(
1004            endpoint,
1005            effective_principal,
1006            canister_id,
1007            sender,
1008            method,
1009            payload,
1010            Some(sender_info),
1011        )
1012        .await
1013    }
1014
1015    /// Execute a query call with sender info on a canister.
1016    pub async fn query_call_with_sender_info(
1017        &self,
1018        canister_id: CanisterId,
1019        sender: Principal,
1020        method: &str,
1021        payload: Vec<u8>,
1022        sender_info: RawSenderInfo,
1023    ) -> Result<Vec<u8>, ClientError> {
1024        self.query_call_with_effective_principal_and_sender_info(
1025            canister_id,
1026            RawEffectivePrincipal::CanisterId(canister_id.as_slice().to_vec()),
1027            sender,
1028            method,
1029            payload,
1030            sender_info,
1031        )
1032        .await
1033    }
1034
1035    /// Fetch canister logs via a query call to the management canister.
1036    pub async fn fetch_canister_logs(
1037        &self,
1038        canister_id: CanisterId,
1039        sender: Principal,
1040    ) -> Result<Vec<CanisterLogRecord>, ClientError> {
1041        Ok(with_candid::<_, (FetchCanisterLogsResult,), _>(
1042            (CanisterIdRecord { canister_id },),
1043            |payload| async {
1044                self.query_call_with_effective_principal(
1045                    Principal::management_canister(),
1046                    RawEffectivePrincipal::CanisterId(canister_id.as_slice().to_vec()),
1047                    sender,
1048                    "fetch_canister_logs",
1049                    payload,
1050                )
1051                .await
1052            },
1053        )
1054        .await?
1055        .0
1056        .canister_log_records)
1057    }
1058
1059    /// Request a canister's status.
1060    #[instrument(skip(self), fields(instance_id=self.instance_id, sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1061    pub async fn canister_status(
1062        &self,
1063        canister_id: CanisterId,
1064        sender: Option<Principal>,
1065    ) -> Result<CanisterStatusResult, ClientError> {
1066        Ok(
1067            call_candid_as::<(CanisterIdRecord,), (CanisterStatusResult,)>(
1068                self,
1069                Principal::management_canister(),
1070                RawEffectivePrincipal::CanisterId(canister_id.as_slice().to_vec()),
1071                sender.unwrap_or(Principal::anonymous()),
1072                "canister_status",
1073                (CanisterIdRecord { canister_id },),
1074            )
1075            .await?
1076            .0,
1077        )
1078    }
1079
1080    /// Create a canister with default settings as the anonymous principal.
1081    /// The canister is created with 100T cycles.
1082    #[instrument(ret, skip(self), fields(instance_id=self.instance_id))]
1083    pub async fn create_canister(&self) -> Result<CanisterId, ClientError> {
1084        self.create_canister_with_params(None, CreateCanisterParams::default())
1085            .await
1086    }
1087
1088    /// Create a canister with optional custom settings and a sender.
1089    /// The canister is created with 100T cycles.
1090    #[instrument(ret, skip(self), fields(instance_id=self.instance_id, settings = ?settings, sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1091    pub async fn create_canister_with_settings(
1092        &self,
1093        sender: Option<Principal>,
1094        settings: Option<CanisterSettings>,
1095    ) -> Result<CanisterId, ClientError> {
1096        self.create_canister_with_params(
1097            sender,
1098            CreateCanisterParams {
1099                settings,
1100                ..Default::default()
1101            },
1102        )
1103        .await
1104    }
1105
1106    /// Creates a canister with a specific canister ID and optional custom settings.
1107    /// The canister is created with 100T cycles.
1108    /// Returns an error if the canister ID is already in use.
1109    /// Creates a new subnet if the canister ID is not contained in any of the subnets.
1110    ///
1111    /// The canister ID must be an IC mainnet canister ID that does not belong to the NNS or II subnet,
1112    /// otherwise the function might panic (for NNS and II canister IDs,
1113    /// the PocketIC instance should already be created with those subnets).
1114    #[instrument(ret, skip(self), fields(instance_id=self.instance_id, sender = %sender.unwrap_or(Principal::anonymous()).to_string(), settings = ?settings, canister_id = %canister_id.to_string()))]
1115    pub async fn create_canister_with_id(
1116        &self,
1117        sender: Option<Principal>,
1118        settings: Option<CanisterSettings>,
1119        canister_id: CanisterId,
1120    ) -> Result<CanisterId, ClientError> {
1121        self.create_canister_with_params(
1122            sender,
1123            CreateCanisterParams {
1124                settings,
1125                placement: Some(CreateCanisterPlacement::CanisterId(canister_id)),
1126                ..Default::default()
1127            },
1128        )
1129        .await
1130    }
1131
1132    /// Create a canister on a specific subnet with optional custom settings.
1133    /// The canister is created with 100T cycles.
1134    #[instrument(ret, skip(self), fields(instance_id=self.instance_id, sender = %sender.unwrap_or(Principal::anonymous()).to_string(), settings = ?settings, subnet_id = %subnet_id.to_string()))]
1135    pub async fn create_canister_on_subnet(
1136        &self,
1137        sender: Option<Principal>,
1138        settings: Option<CanisterSettings>,
1139        subnet_id: SubnetId,
1140    ) -> Result<CanisterId, ClientError> {
1141        self.create_canister_with_params(
1142            sender,
1143            CreateCanisterParams {
1144                settings,
1145                placement: Some(CreateCanisterPlacement::SubnetId(subnet_id)),
1146                ..Default::default()
1147            },
1148        )
1149        .await
1150    }
1151
1152    /// Create a canister with optional cycles, settings, and placement.
1153    /// The placement specifies either a target subnet or a specific canister ID.
1154    /// Defaults to 100T cycles if `params.cycles` is `None`.
1155    /// Returns an error if the specified canister ID is already in use.
1156    #[instrument(ret, skip(self), fields(instance_id=self.instance_id, sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1157    pub async fn create_canister_with_params(
1158        &self,
1159        sender: Option<Principal>,
1160        params: CreateCanisterParams,
1161    ) -> Result<CanisterId, ClientError> {
1162        let cycles = params.cycles.unwrap_or(100_000_000_000_000);
1163        let (effective_principal, specified_id) = match params.placement {
1164            None => (RawEffectivePrincipal::None, None),
1165            Some(CreateCanisterPlacement::SubnetId(subnet_id)) => (
1166                RawEffectivePrincipal::SubnetId(subnet_id.as_slice().to_vec()),
1167                None,
1168            ),
1169            Some(CreateCanisterPlacement::CanisterId(canister_id)) => (
1170                RawEffectivePrincipal::CanisterId(canister_id.as_slice().to_vec()),
1171                Some(canister_id),
1172            ),
1173        };
1174        Ok(call_candid_as::<_, (CanisterIdRecord,)>(
1175            self,
1176            Principal::management_canister(),
1177            effective_principal,
1178            sender.unwrap_or(Principal::anonymous()),
1179            "provisional_create_canister_with_cycles",
1180            (ProvisionalCreateCanisterWithCyclesArgs {
1181                settings: params.settings,
1182                amount: Some(cycles.into()),
1183                specified_id,
1184                sender_canister_version: None,
1185            },),
1186        )
1187        .await?
1188        .0
1189        .canister_id)
1190    }
1191
1192    /// Upload a WASM chunk to the WASM chunk store of a canister.
1193    /// Returns the WASM chunk hash.
1194    #[instrument(skip(self), fields(instance_id=self.instance_id, canister_id = %canister_id.to_string(), sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1195    pub async fn upload_chunk(
1196        &self,
1197        canister_id: CanisterId,
1198        sender: Option<Principal>,
1199        chunk: Vec<u8>,
1200    ) -> Result<Vec<u8>, ClientError> {
1201        Ok(call_candid_as::<_, (UploadChunkResult,)>(
1202            self,
1203            Principal::management_canister(),
1204            RawEffectivePrincipal::CanisterId(canister_id.as_slice().to_vec()),
1205            sender.unwrap_or(Principal::anonymous()),
1206            "upload_chunk",
1207            (UploadChunkArgs { canister_id, chunk },),
1208        )
1209        .await?
1210        .0
1211        .hash)
1212    }
1213
1214    /// List WASM chunk hashes in the WASM chunk store of a canister.
1215    #[instrument(skip(self), fields(instance_id=self.instance_id, canister_id = %canister_id.to_string(), sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1216    pub async fn stored_chunks(
1217        &self,
1218        canister_id: CanisterId,
1219        sender: Option<Principal>,
1220    ) -> Result<Vec<Vec<u8>>, ClientError> {
1221        Ok(call_candid_as::<_, (StoredChunksResult,)>(
1222            self,
1223            Principal::management_canister(),
1224            RawEffectivePrincipal::CanisterId(canister_id.as_slice().to_vec()),
1225            sender.unwrap_or(Principal::anonymous()),
1226            "stored_chunks",
1227            (CanisterIdRecord { canister_id },),
1228        )
1229        .await?
1230        .0
1231        .into_iter()
1232        .map(|chunk| chunk.hash)
1233        .collect())
1234    }
1235
1236    /// Clear the WASM chunk store of a canister.
1237    #[instrument(skip(self), fields(instance_id=self.instance_id, canister_id = %canister_id.to_string(), sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1238    pub async fn clear_chunk_store(
1239        &self,
1240        canister_id: CanisterId,
1241        sender: Option<Principal>,
1242    ) -> Result<(), ClientError> {
1243        call_candid_as(
1244            self,
1245            Principal::management_canister(),
1246            RawEffectivePrincipal::CanisterId(canister_id.as_slice().to_vec()),
1247            sender.unwrap_or(Principal::anonymous()),
1248            "clear_chunk_store",
1249            (CanisterIdRecord { canister_id },),
1250        )
1251        .await
1252    }
1253
1254    /// Install a WASM module assembled from chunks on an existing canister.
1255    #[instrument(skip(self, mode, chunk_hashes_list, wasm_module_hash, arg), fields(instance_id=self.instance_id, canister_id = %canister_id.to_string(), sender = %sender.unwrap_or(Principal::anonymous()).to_string(), store_canister_id = %store_canister_id.to_string(), arg_len = %arg.len()))]
1256    pub async fn install_chunked_canister(
1257        &self,
1258        canister_id: CanisterId,
1259        sender: Option<Principal>,
1260        mode: CanisterInstallMode,
1261        store_canister_id: CanisterId,
1262        chunk_hashes_list: Vec<Vec<u8>>,
1263        wasm_module_hash: Vec<u8>,
1264        arg: Vec<u8>,
1265    ) -> Result<(), ClientError> {
1266        call_candid_as(
1267            self,
1268            Principal::management_canister(),
1269            RawEffectivePrincipal::CanisterId(canister_id.as_slice().to_vec()),
1270            sender.unwrap_or(Principal::anonymous()),
1271            "install_chunked_code",
1272            (InstallChunkedCodeArgs {
1273                mode,
1274                target_canister: canister_id,
1275                store_canister: Some(store_canister_id),
1276                chunk_hashes_list: chunk_hashes_list
1277                    .into_iter()
1278                    .map(|hash| ChunkHash { hash })
1279                    .collect(),
1280                wasm_module_hash,
1281                arg,
1282                sender_canister_version: None,
1283            },),
1284        )
1285        .await
1286    }
1287
1288    async fn install_canister_helper(
1289        &self,
1290        mode: CanisterInstallMode,
1291        canister_id: CanisterId,
1292        wasm_module: Vec<u8>,
1293        arg: Vec<u8>,
1294        sender: Option<Principal>,
1295    ) -> Result<(), ClientError> {
1296        if wasm_module.len() + arg.len() < INSTALL_CHUNKED_CODE_THRESHOLD {
1297            call_candid_as::<(InstallCodeArgs,), ()>(
1298                self,
1299                Principal::management_canister(),
1300                RawEffectivePrincipal::CanisterId(canister_id.as_slice().to_vec()),
1301                sender.unwrap_or(Principal::anonymous()),
1302                "install_code",
1303                (InstallCodeArgs {
1304                    mode,
1305                    canister_id,
1306                    wasm_module,
1307                    arg,
1308                    sender_canister_version: None,
1309                },),
1310            )
1311            .await?
1312        } else {
1313            self.clear_chunk_store(canister_id, sender).await?;
1314            let chunks: Vec<_> = wasm_module.chunks(INSTALL_CODE_CHUNK_SIZE).collect();
1315            let mut hashes = vec![];
1316            for chunk in chunks {
1317                let hash = self
1318                    .upload_chunk(canister_id, sender, chunk.to_vec())
1319                    .await?;
1320                hashes.push(hash);
1321            }
1322            let mut hasher = Sha256::new();
1323            hasher.update(wasm_module);
1324            let wasm_module_hash = hasher.finalize().to_vec();
1325            self.install_chunked_canister(
1326                canister_id,
1327                sender,
1328                mode,
1329                canister_id,
1330                hashes,
1331                wasm_module_hash,
1332                arg,
1333            )
1334            .await?
1335        };
1336        Ok(())
1337    }
1338
1339    /// Install a WASM module on an existing canister.
1340    #[instrument(skip(self, wasm_module, arg), fields(instance_id=self.instance_id, canister_id = %canister_id.to_string(), wasm_module_len = %wasm_module.len(), arg_len = %arg.len(), sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1341    pub async fn install_canister(
1342        &self,
1343        canister_id: CanisterId,
1344        wasm_module: Vec<u8>,
1345        arg: Vec<u8>,
1346        sender: Option<Principal>,
1347    ) -> Result<(), ClientError> {
1348        self.install_canister_helper(
1349            CanisterInstallMode::Install,
1350            canister_id,
1351            wasm_module,
1352            arg,
1353            sender,
1354        )
1355        .await
1356    }
1357
1358    /// Upgrade a canister with a new WASM module.
1359    #[instrument(skip(self, wasm_module, arg), fields(instance_id=self.instance_id, canister_id = %canister_id.to_string(), wasm_module_len = %wasm_module.len(), arg_len = %arg.len(), sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1360    pub async fn upgrade_canister(
1361        &self,
1362        canister_id: CanisterId,
1363        wasm_module: Vec<u8>,
1364        arg: Vec<u8>,
1365        sender: Option<Principal>,
1366    ) -> Result<(), ClientError> {
1367        self.install_canister_helper(
1368            CanisterInstallMode::Upgrade(None),
1369            canister_id,
1370            wasm_module,
1371            arg,
1372            sender,
1373        )
1374        .await
1375    }
1376
1377    /// Upgrade a Motoko EOP canister with a new WASM module.
1378    #[instrument(skip(self, wasm_module, arg), fields(instance_id=self.instance_id, canister_id = %canister_id.to_string(), wasm_module_len = %wasm_module.len(), arg_len = %arg.len(), sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1379    pub async fn upgrade_eop_canister(
1380        &self,
1381        canister_id: CanisterId,
1382        wasm_module: Vec<u8>,
1383        arg: Vec<u8>,
1384        sender: Option<Principal>,
1385    ) -> Result<(), ClientError> {
1386        self.install_canister_helper(
1387            CanisterInstallMode::Upgrade(Some(UpgradeFlags {
1388                wasm_memory_persistence: Some(WasmMemoryPersistence::Keep),
1389                skip_pre_upgrade: None,
1390            })),
1391            canister_id,
1392            wasm_module,
1393            arg,
1394            sender,
1395        )
1396        .await
1397    }
1398
1399    /// Reinstall a canister WASM module.
1400    #[instrument(skip(self, wasm_module, arg), fields(instance_id=self.instance_id, canister_id = %canister_id.to_string(), wasm_module_len = %wasm_module.len(), arg_len = %arg.len(), sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1401    pub async fn reinstall_canister(
1402        &self,
1403        canister_id: CanisterId,
1404        wasm_module: Vec<u8>,
1405        arg: Vec<u8>,
1406        sender: Option<Principal>,
1407    ) -> Result<(), ClientError> {
1408        self.install_canister_helper(
1409            CanisterInstallMode::Reinstall,
1410            canister_id,
1411            wasm_module,
1412            arg,
1413            sender,
1414        )
1415        .await
1416    }
1417
1418    /// Uninstall a canister.
1419    #[instrument(skip(self), fields(instance_id=self.instance_id, canister_id = %canister_id.to_string(), sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1420    pub async fn uninstall_canister(
1421        &self,
1422        canister_id: CanisterId,
1423        sender: Option<Principal>,
1424    ) -> Result<(), ClientError> {
1425        call_candid_as::<(CanisterIdRecord,), ()>(
1426            self,
1427            Principal::management_canister(),
1428            RawEffectivePrincipal::CanisterId(canister_id.as_slice().to_vec()),
1429            sender.unwrap_or(Principal::anonymous()),
1430            "uninstall_code",
1431            (CanisterIdRecord { canister_id },),
1432        )
1433        .await
1434    }
1435
1436    /// Take canister snapshot.
1437    #[instrument(skip(self), fields(instance_id=self.instance_id, canister_id = %canister_id.to_string(), sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1438    pub async fn take_canister_snapshot(
1439        &self,
1440        canister_id: CanisterId,
1441        sender: Option<Principal>,
1442        replace_snapshot: Option<Vec<u8>>,
1443    ) -> Result<Snapshot, ClientError> {
1444        Ok(call_candid_as::<_, (Snapshot,)>(
1445            self,
1446            Principal::management_canister(),
1447            RawEffectivePrincipal::CanisterId(canister_id.as_slice().to_vec()),
1448            sender.unwrap_or(Principal::anonymous()),
1449            "take_canister_snapshot",
1450            (TakeCanisterSnapshotArgs {
1451                canister_id,
1452                replace_snapshot,
1453                uninstall_code: None,
1454                sender_canister_version: None,
1455            },),
1456        )
1457        .await?
1458        .0)
1459    }
1460
1461    /// Load canister snapshot.
1462    #[instrument(skip(self), fields(instance_id=self.instance_id, canister_id = %canister_id.to_string(), sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1463    pub async fn load_canister_snapshot(
1464        &self,
1465        canister_id: CanisterId,
1466        sender: Option<Principal>,
1467        snapshot_id: Vec<u8>,
1468    ) -> Result<(), ClientError> {
1469        call_candid_as(
1470            self,
1471            Principal::management_canister(),
1472            RawEffectivePrincipal::CanisterId(canister_id.as_slice().to_vec()),
1473            sender.unwrap_or(Principal::anonymous()),
1474            "load_canister_snapshot",
1475            (LoadCanisterSnapshotArgs {
1476                canister_id,
1477                snapshot_id,
1478                sender_canister_version: None,
1479            },),
1480        )
1481        .await
1482    }
1483
1484    /// List canister snapshots.
1485    #[instrument(skip(self), fields(instance_id=self.instance_id, canister_id = %canister_id.to_string(), sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1486    pub async fn list_canister_snapshots(
1487        &self,
1488        canister_id: CanisterId,
1489        sender: Option<Principal>,
1490    ) -> Result<Vec<Snapshot>, ClientError> {
1491        Ok(call_candid_as::<_, (Vec<Snapshot>,)>(
1492            self,
1493            Principal::management_canister(),
1494            RawEffectivePrincipal::CanisterId(canister_id.as_slice().to_vec()),
1495            sender.unwrap_or(Principal::anonymous()),
1496            "list_canister_snapshots",
1497            (CanisterIdRecord { canister_id },),
1498        )
1499        .await?
1500        .0)
1501    }
1502
1503    /// Delete canister snapshot.
1504    #[instrument(skip(self), fields(instance_id=self.instance_id, canister_id = %canister_id.to_string(), sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1505    pub async fn delete_canister_snapshot(
1506        &self,
1507        canister_id: CanisterId,
1508        sender: Option<Principal>,
1509        snapshot_id: Vec<u8>,
1510    ) -> Result<(), ClientError> {
1511        call_candid_as(
1512            self,
1513            Principal::management_canister(),
1514            RawEffectivePrincipal::CanisterId(canister_id.as_slice().to_vec()),
1515            sender.unwrap_or(Principal::anonymous()),
1516            "delete_canister_snapshot",
1517            (DeleteCanisterSnapshotArgs {
1518                canister_id,
1519                snapshot_id,
1520            },),
1521        )
1522        .await
1523    }
1524
1525    /// Update canister settings.
1526    #[instrument(skip(self), fields(instance_id=self.instance_id, canister_id = %canister_id.to_string(), sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1527    pub async fn update_canister_settings(
1528        &self,
1529        canister_id: CanisterId,
1530        sender: Option<Principal>,
1531        settings: CanisterSettings,
1532    ) -> Result<(), ClientError> {
1533        call_candid_as::<_, ()>(
1534            self,
1535            Principal::management_canister(),
1536            RawEffectivePrincipal::CanisterId(canister_id.as_slice().to_vec()),
1537            sender.unwrap_or(Principal::anonymous()),
1538            "update_settings",
1539            (UpdateSettingsArgs {
1540                canister_id,
1541                settings,
1542                sender_canister_version: None,
1543            },),
1544        )
1545        .await
1546    }
1547
1548    /// Set canister's controllers.
1549    #[instrument(skip(self), fields(instance_id=self.instance_id, canister_id = %canister_id.to_string(), sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1550    pub async fn set_controllers(
1551        &self,
1552        canister_id: CanisterId,
1553        sender: Option<Principal>,
1554        new_controllers: Vec<Principal>,
1555    ) -> Result<(), ClientError> {
1556        let settings = CanisterSettings {
1557            controllers: Some(new_controllers),
1558            ..CanisterSettings::default()
1559        };
1560        call_candid_as::<(UpdateSettingsArgs,), ()>(
1561            self,
1562            Principal::management_canister(),
1563            RawEffectivePrincipal::CanisterId(canister_id.as_slice().to_vec()),
1564            sender.unwrap_or(Principal::anonymous()),
1565            "update_settings",
1566            (UpdateSettingsArgs {
1567                canister_id,
1568                settings,
1569                sender_canister_version: None,
1570            },),
1571        )
1572        .await
1573    }
1574
1575    /// Start a canister.
1576    #[instrument(skip(self), fields(instance_id=self.instance_id, canister_id = %canister_id.to_string(), sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1577    pub async fn start_canister(
1578        &self,
1579        canister_id: CanisterId,
1580        sender: Option<Principal>,
1581    ) -> Result<(), ClientError> {
1582        call_candid_as::<(CanisterIdRecord,), ()>(
1583            self,
1584            Principal::management_canister(),
1585            RawEffectivePrincipal::CanisterId(canister_id.as_slice().to_vec()),
1586            sender.unwrap_or(Principal::anonymous()),
1587            "start_canister",
1588            (CanisterIdRecord { canister_id },),
1589        )
1590        .await
1591    }
1592
1593    /// Stop a canister.
1594    #[instrument(skip(self), fields(instance_id=self.instance_id, canister_id = %canister_id.to_string(), sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1595    pub async fn stop_canister(
1596        &self,
1597        canister_id: CanisterId,
1598        sender: Option<Principal>,
1599    ) -> Result<(), ClientError> {
1600        call_candid_as::<(CanisterIdRecord,), ()>(
1601            self,
1602            Principal::management_canister(),
1603            RawEffectivePrincipal::CanisterId(canister_id.as_slice().to_vec()),
1604            sender.unwrap_or(Principal::anonymous()),
1605            "stop_canister",
1606            (CanisterIdRecord { canister_id },),
1607        )
1608        .await
1609    }
1610
1611    /// Delete a canister.
1612    #[instrument(skip(self), fields(instance_id=self.instance_id, canister_id = %canister_id.to_string(), sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1613    pub async fn delete_canister(
1614        &self,
1615        canister_id: CanisterId,
1616        sender: Option<Principal>,
1617    ) -> Result<(), ClientError> {
1618        call_candid_as::<(CanisterIdRecord,), ()>(
1619            self,
1620            Principal::management_canister(),
1621            RawEffectivePrincipal::CanisterId(canister_id.as_slice().to_vec()),
1622            sender.unwrap_or(Principal::anonymous()),
1623            "delete_canister",
1624            (CanisterIdRecord { canister_id },),
1625        )
1626        .await
1627    }
1628
1629    /// Checks whether the provided canister exists.
1630    #[instrument(ret, skip(self), fields(instance_id=self.instance_id, canister_id = %canister_id.to_string()))]
1631    pub async fn canister_exists(&self, canister_id: CanisterId) -> Result<bool, ClientError> {
1632        Ok(self.get_subnet(canister_id).await?.is_some())
1633    }
1634
1635    /// Deletes a subnet. Returns a server error if the subnet does not exist or is a named subnet.
1636    #[instrument(ret, skip(self), fields(instance_id=self.instance_id, subnet_id = %subnet_id.to_string()))]
1637    pub async fn delete_subnet(&self, subnet_id: SubnetId) -> Result<(), ClientError> {
1638        let endpoint = "update/delete_subnet";
1639        self.post::<(), RawSubnetId>(
1640            endpoint,
1641            RawSubnetId {
1642                subnet_id: subnet_id.as_slice().to_vec(),
1643            },
1644        )
1645        .await?;
1646
1647        Ok(())
1648    }
1649
1650    /// Returns the subnet ID of the canister if the canister exists.
1651    #[instrument(ret, skip(self), fields(instance_id=self.instance_id, canister_id = %canister_id.to_string()))]
1652    pub async fn get_subnet(
1653        &self,
1654        canister_id: CanisterId,
1655    ) -> Result<Option<SubnetId>, ClientError> {
1656        let endpoint = "read/get_subnet";
1657        let result: Option<RawSubnetId> = self
1658            .post(
1659                endpoint,
1660                RawCanisterId {
1661                    canister_id: canister_id.as_slice().to_vec(),
1662                },
1663            )
1664            .await?;
1665        Ok(result.map(|RawSubnetId { subnet_id }| SubnetId::from_slice(&subnet_id)))
1666    }
1667
1668    /// Returns subnet metrics for a given subnet.
1669    #[instrument(ret, skip(self), fields(instance_id=self.instance_id, subnet_id = %subnet_id.to_string()))]
1670    pub async fn get_subnet_metrics(
1671        &self,
1672        subnet_id: Principal,
1673    ) -> Result<Option<SubnetMetrics>, ClientError> {
1674        let path = vec![
1675            "subnet".into(),
1676            Label::from_bytes(subnet_id.as_slice()),
1677            "metrics".into(),
1678        ];
1679        let paths = vec![path.clone()];
1680        let content = ReadState {
1681            ingress_expiry: self.get_time().await?.as_nanos_since_unix_epoch() + 240_000_000_000,
1682            sender: Principal::anonymous(),
1683            paths,
1684        };
1685        let envelope = Envelope {
1686            content: std::borrow::Cow::Borrowed(&content),
1687            sender_pubkey: None,
1688            sender_sig: None,
1689            sender_delegation: None,
1690        };
1691
1692        let serialized_bytes = encode_envelope(&envelope, usize::MAX)?;
1693
1694        let endpoint = format!("api/v2/subnet/{}/read_state", subnet_id.to_text());
1695        let url = self.instance_url().join(&endpoint).unwrap();
1696        let mut exchange = Exchange::new(&url, self.max_request_time_ms);
1697        let (status, bytes) = exchange
1698            .bytes(
1699                &self.reqwest_client,
1700                self.reqwest_client
1701                    .post(url.clone())
1702                    .header(reqwest::header::CONTENT_TYPE, "application/cbor")
1703                    .body(serialized_bytes),
1704            )
1705            .await?;
1706        if !status.is_success() {
1707            return Err(exchange.server_error(status, &bytes));
1708        }
1709        let read_state_response: ReadStateResponse =
1710            decode_read_state_response(&bytes, bytes.len())?;
1711        let cert: Certificate = decode_certificate(
1712            &read_state_response.certificate,
1713            read_state_response.certificate.len(),
1714        )?;
1715
1716        let metrics = match cert.tree.lookup_path(path) {
1717            LookupResult::Found(value) => value,
1718            _ => return Ok(None),
1719        };
1720        decode_subnet_metrics(metrics, metrics.len()).map_err(ClientError::from)
1721    }
1722
1723    /// This (asynchronous) drop function must be called to drop the PocketIc instance.
1724    /// It must be called manually as Rust doesn't support asynchronous drop.
1725    pub async fn drop(mut self) -> Result<(), ClientError> {
1726        self.do_drop().await?;
1727
1728        Ok(())
1729    }
1730
1731    pub(crate) async fn do_drop(&mut self) -> Result<(), ClientError> {
1732        if self.owns_instance {
1733            let url = self.instance_url();
1734            let mut exchange = Exchange::new(&url, self.max_request_time_ms);
1735            let (status, bytes) = exchange
1736                .bytes(
1737                    &self.reqwest_client,
1738                    self.reqwest_client.delete(url.clone()),
1739                )
1740                .await?;
1741            if !status.is_success() {
1742                return Err(exchange.server_error(status, &bytes));
1743            }
1744            self.owns_instance = false;
1745            Ok(())
1746        } else {
1747            self.stop_http_gateway().await
1748        }
1749    }
1750
1751    async fn get<T: DeserializeOwned>(&self, endpoint: &str) -> Result<T, ClientError> {
1752        self.request(HttpMethod::Get, endpoint, ()).await
1753    }
1754
1755    async fn post<T: DeserializeOwned, B: Serialize>(
1756        &self,
1757        endpoint: &str,
1758        body: B,
1759    ) -> Result<T, ClientError> {
1760        self.request(HttpMethod::Post, endpoint, body).await
1761    }
1762
1763    async fn request<T: DeserializeOwned, B: Serialize>(
1764        &self,
1765        method: HttpMethod,
1766        endpoint: &str,
1767        body: B,
1768    ) -> Result<T, ClientError> {
1769        self.request_limited(method, endpoint, body, None).await
1770    }
1771
1772    async fn request_limited<T: DeserializeOwned, B: Serialize>(
1773        &self,
1774        method: HttpMethod,
1775        endpoint: &str,
1776        body: B,
1777        response_limit: Option<usize>,
1778    ) -> Result<T, ClientError> {
1779        let url = self.instance_url().join(endpoint).unwrap();
1780        let mut exchange = Exchange::new(&url, self.max_request_time_ms);
1781        exchange.response_limit = response_limit;
1782        loop {
1783            let builder = if let Some(operation) = &exchange.context.operation {
1784                self.reqwest_client.get(
1785                    self.server_url
1786                        .join(&format!(
1787                            "/read_graph/{}/{}",
1788                            operation.state_label, operation.op_id
1789                        ))
1790                        .unwrap(),
1791                )
1792            } else {
1793                match method {
1794                    HttpMethod::Get => self.reqwest_client.get(url.clone()),
1795                    HttpMethod::Post => self.reqwest_client.post(url.clone()).json(&body),
1796                }
1797            };
1798            let (status, bytes) = exchange.bytes(&self.reqwest_client, builder).await?;
1799            match status {
1800                StatusCode::OK => {
1801                    let value = exchange.decode(&bytes)?;
1802                    if let Some(operation) = &exchange.context.operation {
1803                        // The result is known; pruning is best-effort housekeeping, never a resubmission.
1804                        let request = self.reqwest_client.delete(
1805                            self.server_url
1806                                .join(&format!(
1807                                    "/prune_graph/{}/{}",
1808                                    operation.state_label, operation.op_id
1809                                ))
1810                                .unwrap(),
1811                        );
1812                        tokio::spawn(async move {
1813                            let _ = request.send().await;
1814                        });
1815                    }
1816                    return Ok(value);
1817                }
1818                StatusCode::ACCEPTED | StatusCode::CONFLICT => {
1819                    let receipt: crate::pocket_ic::common::rest::StartedOrBusyResponse =
1820                        exchange.decode(&bytes)?;
1821                    if exchange.context.operation.is_none() {
1822                        if status == StatusCode::ACCEPTED {
1823                            exchange.started(Operation {
1824                                state_label: receipt.state_label,
1825                                op_id: receipt.op_id,
1826                            });
1827                        } else {
1828                            // Busy explicitly says this submission was not accepted.
1829                            exchange.context.may_have_executed = false;
1830                        }
1831                    }
1832                    // Once accepted, always poll our original receipt, even if an
1833                    // unexpected response refers to a different computation.
1834                }
1835                StatusCode::NOT_FOUND if exchange.context.operation.is_some() => {}
1836                _ => return Err(exchange.server_error(status, &bytes)),
1837            }
1838            exchange.pause().await?;
1839        }
1840    }
1841
1842    async fn canister_call(
1843        &self,
1844        endpoint: &str,
1845        effective_principal: RawEffectivePrincipal,
1846        canister_id: CanisterId,
1847        sender: Principal,
1848        method: &str,
1849        payload: Vec<u8>,
1850        sender_info: Option<RawSenderInfo>,
1851    ) -> Result<Vec<u8>, ClientError> {
1852        let raw_canister_call = RawCanisterCall {
1853            sender: sender.as_slice().to_vec(),
1854            canister_id: canister_id.as_slice().to_vec(),
1855            method: method.to_string(),
1856            payload,
1857            effective_principal,
1858            sender_info,
1859        };
1860
1861        let result: RawCanisterResult = self.post(endpoint, raw_canister_call).await?;
1862        Result::<Vec<u8>, RejectResponse>::from(result).map_err(ClientError::from)
1863    }
1864
1865    pub async fn update_call_with_effective_principal(
1866        &self,
1867        canister_id: CanisterId,
1868        effective_principal: RawEffectivePrincipal,
1869        sender: Principal,
1870        method: &str,
1871        payload: Vec<u8>,
1872    ) -> Result<Vec<u8>, ClientError> {
1873        self.update_call_with_effective_principal_and_sender_info_inner(
1874            canister_id,
1875            effective_principal,
1876            sender,
1877            method,
1878            payload,
1879            None,
1880        )
1881        .await
1882    }
1883
1884    /// Get the pending canister HTTP outcalls.
1885    /// Note that an additional `PocketIc::tick` is necessary after a canister
1886    /// executes a message making a canister HTTP outcall for the HTTP outcall
1887    /// to be retrievable here.
1888    /// Note that, unless a PocketIC instance is in auto progress mode,
1889    /// a response to the pending canister HTTP outcalls
1890    /// must be produced by the test driver and passed on to the PocketIC instace
1891    /// using `PocketIc::mock_canister_http_response`, or, for a *flexible* outcall
1892    /// (`CanisterHttpReplication::Flexible`), using
1893    /// `PocketIc::mock_flexible_canister_http_response`.
1894    /// In auto progress mode, the PocketIC server produces a response for every
1895    /// pending canister HTTP outcall by actually making an HTTP request
1896    /// to the specified URL.
1897    #[instrument(ret, skip(self), fields(instance_id=self.instance_id))]
1898    pub async fn get_canister_http(&self) -> Result<Vec<CanisterHttpRequest>, ClientError> {
1899        let endpoint = "read/get_canister_http";
1900        let res: Vec<RawCanisterHttpRequest> = self.get(endpoint).await?;
1901        Ok(res.into_iter().map(|r| r.into()).collect())
1902    }
1903
1904    /// Mock a response to a pending canister HTTP outcall: the same response for
1905    /// every node of the subnet, or one response per node if
1906    /// `MockCanisterHttpResponse::additional_responses` is non-empty. For a
1907    /// *flexible* outcall, whose committee nodes are answered individually, see
1908    /// `PocketIc::mock_flexible_canister_http_response`.
1909    #[instrument(ret, skip(self), fields(instance_id=self.instance_id))]
1910    pub async fn mock_canister_http_response(
1911        &self,
1912        mock_canister_http_response: MockCanisterHttpResponse,
1913    ) -> Result<(), ClientError> {
1914        let endpoint = "update/mock_canister_http";
1915        let raw_mock_canister_http_response: RawMockCanisterHttpResponse =
1916            mock_canister_http_response.into();
1917        self.post(endpoint, raw_mock_canister_http_response).await
1918    }
1919
1920    /// Mock the responses of the committee nodes of a pending *flexible* canister
1921    /// HTTP outcall, i.e. one made through the `flexible_http_request` management
1922    /// canister endpoint.
1923    ///
1924    /// This takes at most one response per node of the outcall's committee (whose
1925    /// size is the `total_requests` of the outcall's `CanisterHttpReplication::Flexible`
1926    /// replication). Providing fewer responses than the committee size
1927    /// models the remaining committee nodes never responding: with at least
1928    /// `min_responses` successful ones among them the outcall still succeeds, and
1929    /// with fewer it stays pending until the time is advanced past its 60 second
1930    /// timeout, at which point it fails with a timeout error.
1931    ///
1932    /// All responses to an outcall must be provided in a single call: once any
1933    /// response to it has been mocked, the outcall no longer shows up in
1934    /// `PocketIc::get_canister_http` and further responses to it cannot be mocked.
1935    #[instrument(ret, skip(self), fields(instance_id=self.instance_id))]
1936    pub async fn mock_flexible_canister_http_response(
1937        &self,
1938        mock_flexible_canister_http_response: MockFlexibleCanisterHttpResponse,
1939    ) -> Result<(), ClientError> {
1940        let endpoint = "update/mock_flexible_canister_http";
1941        let raw_mock_flexible_canister_http_response: RawMockFlexibleCanisterHttpResponse =
1942            mock_flexible_canister_http_response.into();
1943        self.post(endpoint, raw_mock_flexible_canister_http_response)
1944            .await
1945    }
1946
1947    /// Download a canister snapshot to a given snapshot directory.
1948    /// The sender must be a controller of the canister.
1949    /// The snapshot directory must be empty if it exists.
1950    #[instrument(ret, skip(self), fields(instance_id=self.instance_id))]
1951    pub async fn canister_snapshot_download(
1952        &self,
1953        canister_id: CanisterId,
1954        sender: Principal,
1955        snapshot_id: Vec<u8>,
1956        snapshot_dir: PathBuf,
1957    ) -> Result<(), ClientError> {
1958        let endpoint = "update/canister_snapshot_download";
1959        #[cfg(not(windows))]
1960        let snapshot_dir = snapshot_dir;
1961        #[cfg(windows)]
1962        let snapshot_dir = wsl_path(&snapshot_dir, "snapshot directory").into();
1963        let raw_canister_snapshot_download = RawCanisterSnapshotDownload {
1964            sender: sender.into(),
1965            canister_id: canister_id.into(),
1966            snapshot_id,
1967            snapshot_dir,
1968        };
1969        self.post(endpoint, raw_canister_snapshot_download).await
1970    }
1971
1972    /// Upload a canister snapshot from a given snapshot directory.
1973    /// The sender must be a controller of the canister.
1974    /// Returns the snapshot ID of the uploaded snapshot.
1975    #[instrument(ret, skip(self), fields(instance_id=self.instance_id))]
1976    pub async fn canister_snapshot_upload(
1977        &self,
1978        canister_id: CanisterId,
1979        sender: Principal,
1980        replace_snapshot: Option<Vec<u8>>,
1981        snapshot_dir: PathBuf,
1982    ) -> Result<Vec<u8>, ClientError> {
1983        let endpoint = "update/canister_snapshot_upload";
1984        let replace_snapshot =
1985            replace_snapshot.map(|snapshot_id| RawCanisterSnapshotId { snapshot_id });
1986        #[cfg(not(windows))]
1987        let snapshot_dir = snapshot_dir;
1988        #[cfg(windows)]
1989        let snapshot_dir = wsl_path(&snapshot_dir, "snapshot directory").into();
1990        let raw_canister_snapshot_upload = RawCanisterSnapshotUpload {
1991            sender: sender.into(),
1992            canister_id: canister_id.into(),
1993            replace_snapshot,
1994            snapshot_dir,
1995        };
1996        Ok(self
1997            .post::<RawCanisterSnapshotId, _>(endpoint, raw_canister_snapshot_upload)
1998            .await?
1999            .snapshot_id)
2000    }
2001}
2002
2003/// Call a canister candid method, authenticated. The sender can be impersonated (i.e., the
2004/// signature is not verified).
2005/// PocketIC executes update calls synchronously, so there is no need to poll for the result.
2006pub async fn call_candid_as<Input, Output>(
2007    env: &PocketIc,
2008    canister_id: CanisterId,
2009    effective_principal: RawEffectivePrincipal,
2010    sender: Principal,
2011    method: &str,
2012    input: Input,
2013) -> Result<Output, ClientError>
2014where
2015    Input: ArgumentEncoder,
2016    Output: for<'a> ArgumentDecoder<'a>,
2017{
2018    with_candid(input, |payload| async {
2019        env.update_call_with_effective_principal(
2020            canister_id,
2021            effective_principal,
2022            sender,
2023            method,
2024            payload,
2025        )
2026        .await
2027    })
2028    .await
2029}
2030
2031/// Call a canister candid method, anonymous.
2032/// PocketIC executes update calls synchronously, so there is no need to poll for the result.
2033pub async fn call_candid<Input, Output>(
2034    env: &PocketIc,
2035    canister_id: CanisterId,
2036    effective_principal: RawEffectivePrincipal,
2037    method: &str,
2038    input: Input,
2039) -> Result<Output, ClientError>
2040where
2041    Input: ArgumentEncoder,
2042    Output: for<'a> ArgumentDecoder<'a>,
2043{
2044    call_candid_as(
2045        env,
2046        canister_id,
2047        effective_principal,
2048        Principal::anonymous(),
2049        method,
2050        input,
2051    )
2052    .await
2053}
2054
2055/// Call a canister candid query method, anonymous.
2056pub async fn query_candid<Input, Output>(
2057    env: &PocketIc,
2058    canister_id: CanisterId,
2059    method: &str,
2060    input: Input,
2061) -> Result<Output, ClientError>
2062where
2063    Input: ArgumentEncoder,
2064    Output: for<'a> ArgumentDecoder<'a>,
2065{
2066    query_candid_as(env, canister_id, Principal::anonymous(), method, input).await
2067}
2068
2069/// Call a canister candid query method, authenticated. The sender can be impersonated (i.e., the
2070/// signature is not verified).
2071pub async fn query_candid_as<Input, Output>(
2072    env: &PocketIc,
2073    canister_id: CanisterId,
2074    sender: Principal,
2075    method: &str,
2076    input: Input,
2077) -> Result<Output, ClientError>
2078where
2079    Input: ArgumentEncoder,
2080    Output: for<'a> ArgumentDecoder<'a>,
2081{
2082    with_candid(input, |bytes| async {
2083        env.query_call(canister_id, sender, method, bytes).await
2084    })
2085    .await
2086}
2087
2088/// Call a canister candid update method, anonymous.
2089pub async fn update_candid<Input, Output>(
2090    env: &PocketIc,
2091    canister_id: CanisterId,
2092    method: &str,
2093    input: Input,
2094) -> Result<Output, ClientError>
2095where
2096    Input: ArgumentEncoder,
2097    Output: for<'a> ArgumentDecoder<'a>,
2098{
2099    update_candid_as(env, canister_id, Principal::anonymous(), method, input).await
2100}
2101
2102/// Call a canister candid update method, authenticated. The sender can be impersonated (i.e., the
2103/// signature is not verified).
2104pub async fn update_candid_as<Input, Output>(
2105    env: &PocketIc,
2106    canister_id: CanisterId,
2107    sender: Principal,
2108    method: &str,
2109    input: Input,
2110) -> Result<Output, ClientError>
2111where
2112    Input: ArgumentEncoder,
2113    Output: for<'a> ArgumentDecoder<'a>,
2114{
2115    with_candid(input, |bytes| async {
2116        env.update_call(canister_id, sender, method, bytes).await
2117    })
2118    .await
2119}
2120
2121/// A helper function that we use to implement both [`call_candid`] and
2122/// [`query_candid`].
2123pub async fn with_candid<Input, Output, Fut>(
2124    input: Input,
2125    f: impl FnOnce(Vec<u8>) -> Fut,
2126) -> Result<Output, ClientError>
2127where
2128    Input: ArgumentEncoder,
2129    Output: for<'a> ArgumentDecoder<'a>,
2130    Fut: Future<Output = Result<Vec<u8>, ClientError>>,
2131{
2132    let input = encode_args(input)?;
2133    let output = f(input).await?;
2134    Ok(decode_args(&output)?)
2135}
2136
2137fn setup_tracing(pid: u32) -> Option<WorkerGuard> {
2138    use tracing_subscriber::prelude::*;
2139    match std::env::var(LOG_DIR_PATH_ENV_NAME).map(std::path::PathBuf::from) {
2140        Ok(p) => {
2141            std::fs::create_dir_all(&p).expect("Could not create directory");
2142
2143            let file_name = format!("pocket_ic_client_{pid}.log");
2144            let appender = tracing_appender::rolling::never(&p, file_name);
2145            let (non_blocking_appender, guard) = tracing_appender::non_blocking(appender);
2146            let log_dir_filter: EnvFilter =
2147                tracing_subscriber::EnvFilter::try_from_env(LOG_DIR_LEVELS_ENV_NAME)
2148                    .unwrap_or_else(|_| "trace".parse().unwrap());
2149
2150            let layers = vec![
2151                tracing_subscriber::fmt::layer()
2152                    .with_writer(non_blocking_appender)
2153                    // disable color escape codes in files
2154                    .with_ansi(false)
2155                    .with_filter(log_dir_filter)
2156                    .boxed(),
2157            ];
2158            let _ = tracing_subscriber::registry().with(layers).try_init();
2159            Some(guard)
2160        }
2161        _ => None,
2162    }
2163}
2164
2165/// Retrieves a default effective canister id for canister creation on a PocketIC instance
2166/// characterized by:
2167///  - a PocketIC instance URL of the form `http://<ip>:<port>/instances/<instance_id>`;
2168///  - a PocketIC HTTP gateway URL of the form `http://<ip>:port` for a PocketIC instance.
2169///
2170/// Returns an error if the PocketIC instance topology could not be fetched or parsed, e.g.,
2171/// because the given URL points to a replica (i.e., does not meet any of the above two properties).
2172pub async fn get_default_effective_canister_id(
2173    pocket_ic_url: String,
2174) -> Result<Principal, ClientError> {
2175    let url = Url::parse(&format!(
2176        "{}/_/topology",
2177        pocket_ic_url.trim_end_matches('/')
2178    ))
2179    .map_err(|e| ClientError::Protocol(e.to_string()))?;
2180    let client = reqwest::Client::new();
2181    let mut exchange = Exchange::new(&url, Some(crate::pocket_ic::DEFAULT_MAX_REQUEST_TIME_MS));
2182    loop {
2183        let (status, bytes) = exchange.bytes(&client, client.get(url.clone())).await?;
2184        if status == StatusCode::CONFLICT {
2185            exchange.pause().await?;
2186            continue;
2187        }
2188        if !status.is_success() {
2189            return Err(exchange.server_error(status, &bytes));
2190        }
2191        let topology: Topology = exchange.decode(&bytes)?;
2192        return Principal::try_from_slice(&topology.default_effective_canister_id.canister_id)
2193            .map_err(|e| ClientError::Protocol(e.to_string()));
2194    }
2195}