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