Skip to main content

pocket_ic/
nonblocking.rs

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