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