Skip to main content

ic_testkit/pocket_ic/
mod.rs

1#![cfg(not(target_family = "wasm"))]
2#![expect(
3    missing_docs,
4    clippy::too_many_arguments,
5    clippy::pedantic,
6    clippy::nursery,
7    reason = "initial upstream source transfer preserves public signatures and protocol names; see upstream/NOTICE.md"
8)]
9// Copyright DFINITY Foundation. Licensed under Apache-2.0.
10//! Maintained PocketIC 16.1 client implementation; see ../upstream/NOTICE.md.
11/// # PocketIC: A Canister Testing Platform
12///
13/// PocketIC is the local canister smart contract testing platform for the [Internet Computer](https://internetcomputer.org/).
14///
15/// It consists of the PocketIC server, which can run many independent IC instances, and a client library (this crate), which provides an interface to your IC instances.
16///
17/// With PocketIC, testing canisters is as simple as calling rust functions. Here is a minimal example:
18///
19/// ```ignore
20/// use candid::{Principal, encode_one};
21/// use pocket_ic::PocketIc;
22///
23/// // 2T cycles
24/// const INIT_CYCLES: u128 = 2_000_000_000_000;
25///
26/// // Create a counter canister and charge it with 2T cycles.
27/// fn deploy_counter_canister(pic: &PocketIc) -> Principal {
28///     let canister_id = pic.create_canister();
29///     pic.add_cycles(canister_id, INIT_CYCLES);
30///     let counter_wasm = todo!();
31///     pic.install_canister(canister_id, counter_wasm, vec![], None);
32///     canister_id
33/// }
34///
35/// // Call a method on the counter canister as the anonymous principal.
36/// fn call_counter_canister(pic: &PocketIc, canister_id: Principal, method: &str) -> Vec<u8> {
37///     pic.update_call(
38///         canister_id,
39///         Principal::anonymous(),
40///         method,
41///         encode_one(()).unwrap(),
42///     )
43///     .expect("Failed to call counter canister")
44/// }
45///
46/// #[test]
47/// fn test_counter_canister() {
48///     let pic = PocketIc::new();
49///     let canister_id = deploy_counter_canister(&pic);
50///
51///     // Make some calls to the counter canister.
52///     let reply = call_counter_canister(&pic, canister_id, "read");
53///     assert_eq!(reply, vec![0, 0, 0, 0]);
54///     let reply = call_counter_canister(&pic, canister_id, "write");
55///     assert_eq!(reply, vec![1, 0, 0, 0]);
56///     let reply = call_counter_canister(&pic, canister_id, "write");
57///     assert_eq!(reply, vec![2, 0, 0, 0]);
58///     let reply = call_counter_canister(&pic, canister_id, "read");
59///     assert_eq!(reply, vec![2, 0, 0, 0]);
60/// }
61/// ```
62/// For more information, see the [README](https://crates.io/crates/pocket-ic).
63///
64use crate::pocket_ic::{
65    common::rest::{
66        AutoProgressConfig, BlobCompression, BlobId, CanisterHttpRequest, ExtendedSubnetConfigSet,
67        HttpsConfig, IcpConfig, IcpFeatures, InitialTime, InstanceHttpGatewayConfig, InstanceId,
68        MockCanisterHttpResponse, MockFlexibleCanisterHttpResponse, RawEffectivePrincipal,
69        RawMessageId, RawSenderInfo, RawSubnetBlockmakers, RawTickConfigs, RawTime, SubnetId,
70        SubnetKind, SubnetSpec, Topology,
71    },
72    nonblocking::PocketIc as PocketIcAsync,
73};
74use candid::{
75    Principal, decode_args, encode_args,
76    utils::{ArgumentDecoder, ArgumentEncoder},
77};
78use flate2::read::GzDecoder;
79pub use ic_management_canister_types::{
80    CanisterId, CanisterInstallMode, CanisterLogRecord, CanisterSettings, CanisterStatusResult,
81    EnvironmentVariable, Snapshot,
82};
83pub use ic_transport_types::SubnetMetrics;
84use reqwest::Url;
85use schemars::JsonSchema;
86use semver::{Version, VersionReq};
87use serde::{Deserialize, Serialize};
88use slog::Level;
89#[cfg(unix)]
90use std::os::unix::fs::OpenOptionsExt;
91#[cfg(windows)]
92use std::sync::Once;
93use std::{
94    fs::OpenOptions,
95    net::{IpAddr, SocketAddr},
96    path::PathBuf,
97    process::{Child, Command},
98    sync::{Arc, mpsc::channel},
99    thread,
100    thread::JoinHandle,
101    time::{Duration, SystemTime, UNIX_EPOCH},
102};
103use strum_macros::EnumIter;
104use tempfile::{NamedTempFile, TempDir};
105use tokio::runtime::Runtime;
106use tracing::{instrument, warn};
107
108mod error;
109mod http;
110pub use error::{ClientError, Operation, RequestContext};
111pub mod common;
112pub mod nonblocking;
113pub mod snapshot;
114
115const POCKET_IC_SERVER_NAME: &str = "pocket-ic-server";
116
117const MIN_SERVER_VERSION: &str = "16.0.0";
118const MAX_SERVER_VERSION: &str = "17";
119
120/// Public to facilitate downloading the PocketIC server.
121pub const LATEST_SERVER_VERSION: &str = "16.1.0";
122
123// the default timeout of a PocketIC operation
124const DEFAULT_MAX_REQUEST_TIME_MS: u64 = 300_000;
125
126const LOCALHOST: &str = "127.0.0.1";
127
128enum PocketIcStateKind {
129    /// A persistent state dir managed by the user.
130    StateDir(PathBuf),
131    /// A fresh temporary directory used if the user does not provide
132    /// a persistent state directory managed by the user.
133    /// The temporary directory is deleted when `PocketIcState` is dropped
134    /// unless `PocketIcState` is turned into a persistent state
135    /// at the path given by `PocketIcState::into_path`.
136    TempDir(TempDir),
137}
138
139pub struct PocketIcState {
140    state: PocketIcStateKind,
141}
142
143impl PocketIcState {
144    #[expect(
145        clippy::new_without_default,
146        reason = "preserve the imported PocketIC contract without an unrelated API change"
147    )]
148    pub fn new() -> Self {
149        let temp_dir = TempDir::new().unwrap();
150        Self {
151            state: PocketIcStateKind::TempDir(temp_dir),
152        }
153    }
154
155    pub fn new_from_path(state_dir: PathBuf) -> Self {
156        Self {
157            state: PocketIcStateKind::StateDir(state_dir),
158        }
159    }
160
161    pub fn into_path(self) -> PathBuf {
162        match self.state {
163            PocketIcStateKind::StateDir(state_dir) => state_dir,
164            PocketIcStateKind::TempDir(temp_dir) => temp_dir.keep(),
165        }
166    }
167
168    pub(crate) fn state_dir(&self) -> PathBuf {
169        match &self.state {
170            PocketIcStateKind::StateDir(state_dir) => state_dir.clone(),
171            PocketIcStateKind::TempDir(temp_dir) => temp_dir.path().to_path_buf(),
172        }
173    }
174}
175
176pub struct PocketIcBuilder {
177    config: Option<ExtendedSubnetConfigSet>,
178    http_gateway_config: Option<InstanceHttpGatewayConfig>,
179    server_binary: Option<PathBuf>,
180    server_url: Option<Url>,
181    max_request_time_ms: Option<u64>,
182    read_only_state_dir: Option<PathBuf>,
183    state_dir: Option<PocketIcState>,
184    icp_config: IcpConfig,
185    log_level: Option<Level>,
186    bitcoind_addr: Option<Vec<SocketAddr>>,
187    dogecoind_addr: Option<Vec<SocketAddr>>,
188    icp_features: IcpFeatures,
189    initial_time: Option<InitialTime>,
190    mainnet_nns_subnet_id: Option<bool>,
191    disable_ingress_validation: Option<bool>,
192}
193
194#[expect(
195    clippy::new_without_default,
196    reason = "preserve the imported PocketIC contract without an unrelated API change"
197)]
198impl PocketIcBuilder {
199    pub fn new() -> Self {
200        Self {
201            config: None,
202            http_gateway_config: None,
203            server_binary: None,
204            server_url: None,
205            max_request_time_ms: Some(DEFAULT_MAX_REQUEST_TIME_MS),
206            read_only_state_dir: None,
207            state_dir: None,
208            icp_config: IcpConfig::default(),
209            log_level: None,
210            bitcoind_addr: None,
211            dogecoind_addr: None,
212            icp_features: IcpFeatures::default(),
213            initial_time: None,
214            mainnet_nns_subnet_id: None,
215            disable_ingress_validation: None,
216        }
217    }
218
219    pub fn new_with_config(config: impl Into<ExtendedSubnetConfigSet>) -> Self {
220        let mut builder = Self::new();
221        builder.config = Some(config.into());
222        builder
223    }
224
225    pub fn build(self) -> Result<PocketIc, ClientError> {
226        PocketIc::from_components(
227            self.config.unwrap_or_default(),
228            self.server_url,
229            self.server_binary,
230            self.max_request_time_ms,
231            self.read_only_state_dir,
232            self.state_dir,
233            self.icp_config,
234            self.log_level,
235            self.bitcoind_addr,
236            self.dogecoind_addr,
237            self.icp_features,
238            self.initial_time,
239            self.http_gateway_config,
240            self.mainnet_nns_subnet_id,
241            self.disable_ingress_validation,
242        )
243    }
244
245    pub async fn build_async(self) -> Result<PocketIcAsync, ClientError> {
246        PocketIcAsync::from_components(
247            self.config.unwrap_or_default(),
248            self.server_url,
249            self.server_binary,
250            self.max_request_time_ms,
251            self.read_only_state_dir,
252            self.state_dir,
253            self.icp_config,
254            self.log_level,
255            self.bitcoind_addr,
256            self.dogecoind_addr,
257            self.icp_features,
258            self.initial_time,
259            self.http_gateway_config,
260            self.mainnet_nns_subnet_id,
261            self.disable_ingress_validation,
262        )
263        .await
264    }
265
266    /// Provide the path to the PocketIC server binary used instead of the environment variable `POCKET_IC_BIN`.
267    pub fn with_server_binary(mut self, server_binary: PathBuf) -> Self {
268        self.server_binary = Some(server_binary);
269        self
270    }
271
272    /// Use an already running PocketIC server.
273    pub fn with_server_url(mut self, server_url: Url) -> Self {
274        self.server_url = Some(server_url);
275        self
276    }
277
278    pub fn with_max_request_time_ms(mut self, max_request_time_ms: Option<u64>) -> Self {
279        self.max_request_time_ms = max_request_time_ms;
280        self
281    }
282
283    pub fn with_state_dir(mut self, state_dir: PathBuf) -> Self {
284        self.state_dir = Some(PocketIcState::new_from_path(state_dir));
285        self
286    }
287
288    pub fn with_state(mut self, state_dir: PocketIcState) -> Self {
289        self.state_dir = Some(state_dir);
290        self
291    }
292
293    pub fn with_read_only_state(mut self, read_only_state_dir: &PocketIcState) -> Self {
294        self.read_only_state_dir = Some(read_only_state_dir.state_dir());
295        self
296    }
297
298    pub fn with_icp_config(mut self, icp_config: IcpConfig) -> Self {
299        self.icp_config = icp_config;
300        self
301    }
302
303    pub fn with_log_level(mut self, log_level: Level) -> Self {
304        self.log_level = Some(log_level);
305        self
306    }
307
308    pub fn with_bitcoind_addr(self, bitcoind_addr: SocketAddr) -> Self {
309        self.with_bitcoind_addrs(vec![bitcoind_addr])
310    }
311
312    pub fn with_bitcoind_addrs(self, bitcoind_addrs: Vec<SocketAddr>) -> Self {
313        Self {
314            bitcoind_addr: Some(bitcoind_addrs),
315            ..self
316        }
317    }
318
319    pub fn with_dogecoind_addrs(self, dogecoind_addrs: Vec<SocketAddr>) -> Self {
320        Self {
321            dogecoind_addr: Some(dogecoind_addrs),
322            ..self
323        }
324    }
325
326    /// Add an empty NNS subnet unless an NNS subnet has already been added.
327    pub fn with_nns_subnet(mut self) -> Self {
328        let mut config = self.config.unwrap_or_default();
329        config.nns = Some(config.nns.unwrap_or_default());
330        self.config = Some(config);
331        self
332    }
333
334    /// Add an NNS subnet with state loaded from the given state directory.
335    /// Note that the provided path must be accessible for the PocketIC server process.
336    ///
337    /// `path_to_state` should lead to a directory which is expected to have
338    /// the following structure:
339    ///
340    /// path_to_state/
341    ///  |-- backups
342    ///  |-- checkpoints
343    ///  |-- diverged_checkpoints
344    ///  |-- diverged_state_markers
345    ///  |-- fs_tmp
346    ///  |-- page_deltas
347    ///  |-- states_metadata.pbuf
348    ///  |-- tip
349    ///  `-- tmp
350    pub fn with_nns_state(self, path_to_state: PathBuf) -> Self {
351        self.with_subnet_state(SubnetKind::NNS, path_to_state)
352    }
353
354    /// Add a subnet with state loaded from the given state directory.
355    /// Note that the provided path must be accessible for the PocketIC server process.
356    ///
357    /// `path_to_state` should point to a directory which is expected to have
358    /// the following structure:
359    ///
360    /// path_to_state/
361    ///  |-- backups
362    ///  |-- checkpoints
363    ///  |-- diverged_checkpoints
364    ///  |-- diverged_state_markers
365    ///  |-- fs_tmp
366    ///  |-- page_deltas
367    ///  |-- states_metadata.pbuf
368    ///  |-- tip
369    ///  `-- tmp
370    pub fn with_subnet_state(mut self, subnet_kind: SubnetKind, path_to_state: PathBuf) -> Self {
371        let mut config = self.config.unwrap_or_default();
372        #[cfg(not(windows))]
373        let state_dir = path_to_state;
374        #[cfg(windows)]
375        let state_dir = wsl_path(&path_to_state, "subnet state").into();
376        let subnet_spec = SubnetSpec::default().with_state_dir(state_dir);
377        match subnet_kind {
378            SubnetKind::NNS => config.nns = Some(subnet_spec),
379            SubnetKind::SNS => config.sns = Some(subnet_spec),
380            SubnetKind::II => config.ii = Some(subnet_spec),
381            SubnetKind::Fiduciary => config.fiduciary = Some(subnet_spec),
382            SubnetKind::Bitcoin => config.bitcoin = Some(subnet_spec),
383            SubnetKind::TestThresholdKeys => config.test_threshold_keys = Some(subnet_spec),
384            SubnetKind::Application => config.application.push(subnet_spec),
385            SubnetKind::CloudEngine => config.cloud_engine.push(subnet_spec),
386            SubnetKind::System => config.system.push(subnet_spec),
387            SubnetKind::VerifiedApplication => config.verified_application.push(subnet_spec),
388        };
389        self.config = Some(config);
390        self
391    }
392
393    /// Add an empty sns subnet unless an SNS subnet has already been added.
394    pub fn with_sns_subnet(mut self) -> Self {
395        let mut config = self.config.unwrap_or_default();
396        config.sns = Some(config.sns.unwrap_or_default());
397        self.config = Some(config);
398        self
399    }
400
401    /// Add an empty II subnet unless an II subnet has already been added.
402    pub fn with_ii_subnet(mut self) -> Self {
403        let mut config = self.config.unwrap_or_default();
404        config.ii = Some(config.ii.unwrap_or_default());
405        self.config = Some(config);
406        self
407    }
408
409    /// Add an empty fiduciary subnet unless a fiduciary subnet has already been added.
410    pub fn with_fiduciary_subnet(mut self) -> Self {
411        let mut config = self.config.unwrap_or_default();
412        config.fiduciary = Some(config.fiduciary.unwrap_or_default());
413        self.config = Some(config);
414        self
415    }
416
417    /// Add an empty bitcoin subnet unless a bitcoin subnet has already been added.
418    pub fn with_bitcoin_subnet(mut self) -> Self {
419        let mut config = self.config.unwrap_or_default();
420        config.bitcoin = Some(config.bitcoin.unwrap_or_default());
421        self.config = Some(config);
422        self
423    }
424
425    /// Add an empty test threshold keys subnet unless a test threshold keys subnet has already been added.
426    pub fn with_test_threshold_keys_subnet(mut self) -> Self {
427        let mut config = self.config.unwrap_or_default();
428        config.test_threshold_keys = Some(config.test_threshold_keys.unwrap_or_default());
429        self.config = Some(config);
430        self
431    }
432
433    /// Add an empty generic system subnet.
434    pub fn with_system_subnet(mut self) -> Self {
435        let mut config = self.config.unwrap_or_default();
436        config.system.push(SubnetSpec::default());
437        self.config = Some(config);
438        self
439    }
440
441    /// Add an empty generic application subnet.
442    pub fn with_application_subnet(mut self) -> Self {
443        let mut config = self.config.unwrap_or_default();
444        config.application.push(SubnetSpec::default());
445        self.config = Some(config);
446        self
447    }
448
449    /// Add an empty generic verified application subnet.
450    pub fn with_verified_application_subnet(mut self) -> Self {
451        let mut config = self.config.unwrap_or_default();
452        config.verified_application.push(SubnetSpec::default());
453        self.config = Some(config);
454        self
455    }
456
457    /// Add an empty generic application subnet with benchmarking instruction configuration.
458    pub fn with_benchmarking_application_subnet(mut self) -> Self {
459        let mut config = self.config.unwrap_or_default();
460        config
461            .application
462            .push(SubnetSpec::default().with_benchmarking_instruction_config());
463        self.config = Some(config);
464        self
465    }
466
467    /// Add an empty generic system subnet with benchmarking instruction configuration.
468    pub fn with_benchmarking_system_subnet(mut self) -> Self {
469        let mut config = self.config.unwrap_or_default();
470        config
471            .system
472            .push(SubnetSpec::default().with_benchmarking_instruction_config());
473        self.config = Some(config);
474        self
475    }
476
477    /// Enables selected ICP features supported by PocketIC and implemented by system canisters
478    /// (deployed to the PocketIC instance automatically when creating a new PocketIC instance).
479    /// Subnets to which the system canisters are deployed are automatically declared as empty subnets,
480    /// e.g., `PocketIcBuilder::with_nns_subnet` is implicitly implied by specifying the `icp_token` feature.
481    pub fn with_icp_features(mut self, icp_features: IcpFeatures) -> Self {
482        self.icp_features = icp_features;
483        self
484    }
485
486    /// Sets the initial timestamp of the new instance to the provided value which must be at least
487    /// - 10 May 2021 10:00:01 AM CEST if the `cycles_minting` feature is enabled in `icp_features`;
488    /// - 06 May 2021 21:17:10 CEST otherwise.
489    #[deprecated(note = "Use `with_initial_time` instead")]
490    pub fn with_initial_timestamp(mut self, initial_timestamp_nanos: u64) -> Self {
491        self.initial_time = Some(InitialTime::Timestamp(RawTime {
492            nanos_since_epoch: initial_timestamp_nanos,
493        }));
494        self
495    }
496
497    /// Sets the initial time of the new instance to the provided value which must be at least
498    /// - 10 May 2021 10:00:01 AM CEST if the `cycles_minting` feature is enabled in `icp_features`;
499    /// - 06 May 2021 21:17:10 CEST otherwise.
500    pub fn with_initial_time(mut self, initial_time: Time) -> Self {
501        self.initial_time = Some(InitialTime::Timestamp(RawTime {
502            nanos_since_epoch: initial_time.as_nanos_since_unix_epoch(),
503        }));
504        self
505    }
506
507    /// Configures the new instance to make progress automatically,
508    /// i.e., periodically update the time of the IC instance
509    /// to the real time and execute rounds on the subnets.
510    /// Building the instance only returns after the certified time
511    /// of the IC instance has been updated for the first time.
512    pub fn with_auto_progress(mut self) -> Self {
513        let config = AutoProgressConfig {
514            artificial_delay_ms: None,
515        };
516        self.initial_time = Some(InitialTime::AutoProgress(config));
517        self
518    }
519
520    pub fn with_http_gateway(mut self, http_gateway_config: InstanceHttpGatewayConfig) -> Self {
521        self.http_gateway_config = Some(http_gateway_config);
522        self
523    }
524
525    pub fn with_mainnet_nns_subnet_id(mut self) -> Self {
526        self.mainnet_nns_subnet_id = Some(true);
527        self
528    }
529
530    pub fn disable_ingress_validation(mut self) -> Self {
531        self.disable_ingress_validation = Some(true);
532        self
533    }
534}
535
536/// Representation of system time as duration since UNIX epoch
537/// with cross-platform nanosecond precision.
538#[derive(Copy, Clone, PartialEq, PartialOrd)]
539pub struct Time(Duration);
540
541impl Time {
542    /// Number of nanoseconds since UNIX EPOCH.
543    pub fn as_nanos_since_unix_epoch(&self) -> u64 {
544        self.0.as_nanos().try_into().unwrap()
545    }
546
547    pub const fn from_nanos_since_unix_epoch(nanos: u64) -> Self {
548        Time(Duration::from_nanos(nanos))
549    }
550}
551
552impl std::fmt::Debug for Time {
553    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
554        let nanos_since_unix_epoch = self.as_nanos_since_unix_epoch();
555        write!(f, "{nanos_since_unix_epoch}")
556    }
557}
558
559impl std::ops::Add<Duration> for Time {
560    type Output = Time;
561    fn add(self, dur: Duration) -> Time {
562        Time(self.0 + dur)
563    }
564}
565
566impl From<SystemTime> for Time {
567    fn from(time: SystemTime) -> Self {
568        Self::from_nanos_since_unix_epoch(
569            time.duration_since(UNIX_EPOCH)
570                .unwrap()
571                .as_nanos()
572                .try_into()
573                .unwrap(),
574        )
575    }
576}
577
578impl TryFrom<Time> for SystemTime {
579    type Error = String;
580
581    fn try_from(time: Time) -> Result<SystemTime, String> {
582        let nanos = time.as_nanos_since_unix_epoch();
583        let system_time = UNIX_EPOCH + Duration::from_nanos(nanos);
584        let roundtrip: Time = system_time.into();
585        if roundtrip.as_nanos_since_unix_epoch() == nanos {
586            Ok(system_time)
587        } else {
588            Err(format!(
589                "Converting UNIX timestamp {nanos} in nanoseconds to SystemTime failed due to losing precision"
590            ))
591        }
592    }
593}
594
595/// Specifies where to place a newly created canister.
596#[derive(Clone, Debug)]
597pub enum CreateCanisterPlacement {
598    /// Place the canister on the given subnet.
599    SubnetId(SubnetId),
600    /// Create the canister with the given specific canister ID.
601    CanisterId(CanisterId),
602}
603
604/// Parameters for [`PocketIc::create_canister_with_params`].
605#[derive(Clone, Debug, Default)]
606pub struct CreateCanisterParams {
607    /// Initial cycles balance; defaults to 100T if `None`.
608    pub cycles: Option<u128>,
609    /// Canister settings; defaults to default canister settings if `None`.
610    pub settings: Option<CanisterSettings>,
611    /// Canister placement (subnet or specific canister ID); a random application subnet is chosen if `None`.
612    pub placement: Option<CreateCanisterPlacement>,
613}
614
615/// Main entry point for interacting with PocketIC.
616pub struct PocketIc {
617    pocket_ic: PocketIcAsync,
618    runtime: Arc<tokio::runtime::Runtime>,
619    thread: Option<JoinHandle<()>>,
620}
621
622impl PocketIc {
623    /// Creates a new PocketIC instance with a single application subnet on the server.
624    /// The server is started if it's not already running.
625    pub fn new() -> Result<Self, ClientError> {
626        PocketIcBuilder::new().with_application_subnet().build()
627    }
628
629    /// Creates a PocketIC handle to an existing instance on a running server.
630    /// Note that this handle does not extend the lifetime of the existing instance,
631    /// i.e., the existing instance is deleted and this handle stops working
632    /// when the PocketIC handle that created the existing instance is dropped.
633    pub fn new_from_existing_instance(
634        server_url: Url,
635        instance_id: InstanceId,
636        max_request_time_ms: Option<u64>,
637    ) -> Self {
638        let (tx, rx) = channel();
639        let thread = thread::spawn(move || {
640            let rt = tokio::runtime::Builder::new_current_thread()
641                .enable_all()
642                .build()
643                .unwrap();
644            tx.send(rt).unwrap();
645        });
646        let runtime = rx.recv().unwrap();
647
648        let pocket_ic =
649            PocketIcAsync::new_from_existing_instance(server_url, instance_id, max_request_time_ms);
650
651        Self {
652            pocket_ic,
653            runtime: Arc::new(runtime),
654            thread: Some(thread),
655        }
656    }
657
658    #[expect(
659        clippy::too_many_arguments,
660        reason = "retained upstream client method signature"
661    )]
662    pub(crate) fn from_components(
663        subnet_config_set: impl Into<ExtendedSubnetConfigSet>,
664        server_url: Option<Url>,
665        server_binary: Option<PathBuf>,
666        max_request_time_ms: Option<u64>,
667        read_only_state_dir: Option<PathBuf>,
668        state_dir: Option<PocketIcState>,
669        icp_config: IcpConfig,
670        log_level: Option<Level>,
671        bitcoind_addr: Option<Vec<SocketAddr>>,
672        dogecoind_addr: Option<Vec<SocketAddr>>,
673        icp_features: IcpFeatures,
674        initial_time: Option<InitialTime>,
675        http_gateway_config: Option<InstanceHttpGatewayConfig>,
676        mainnet_nns_subnet_id: Option<bool>,
677        disable_ingress_validation: Option<bool>,
678    ) -> Result<Self, ClientError> {
679        let (tx, rx) = channel();
680        let thread = thread::spawn(move || {
681            let rt = tokio::runtime::Builder::new_current_thread()
682                .enable_all()
683                .build()
684                .unwrap();
685            tx.send(rt).unwrap();
686        });
687        let runtime = rx.recv().unwrap();
688
689        let pocket_ic = runtime.block_on(async {
690            PocketIcAsync::from_components(
691                subnet_config_set,
692                server_url,
693                server_binary,
694                max_request_time_ms,
695                read_only_state_dir,
696                state_dir,
697                icp_config,
698                log_level,
699                bitcoind_addr,
700                dogecoind_addr,
701                icp_features,
702                initial_time,
703                http_gateway_config,
704                mainnet_nns_subnet_id,
705                disable_ingress_validation,
706            )
707            .await
708        })?;
709
710        Ok(Self {
711            pocket_ic,
712            runtime: Arc::new(runtime),
713            thread: Some(thread),
714        })
715    }
716
717    pub fn drop_and_take_state(&mut self) -> Result<Option<PocketIcState>, ClientError> {
718        self.runtime.block_on(self.pocket_ic.do_drop())?;
719        Ok(self.pocket_ic.take_state_internal())
720    }
721
722    /// Returns the URL of the PocketIC server on which this PocketIC instance is running.
723    pub fn get_server_url(&self) -> Url {
724        self.pocket_ic.get_server_url()
725    }
726
727    /// Returns the instance ID.
728    pub fn instance_id(&self) -> InstanceId {
729        self.pocket_ic.instance_id
730    }
731
732    /// Returns the topology of the different subnets of this PocketIC instance.
733    pub fn topology(&self) -> Result<Topology, ClientError> {
734        let runtime = self.runtime.clone();
735        runtime.block_on(async { self.pocket_ic.topology().await })
736    }
737
738    /// Upload and store a binary blob to the PocketIC server.
739    #[instrument(ret, skip(self, blob), fields(instance_id=self.pocket_ic.instance_id, blob_len = %blob.len(), compression = ?compression))]
740    pub fn upload_blob(
741        &self,
742        blob: Vec<u8>,
743        compression: BlobCompression,
744    ) -> Result<BlobId, ClientError> {
745        let runtime = self.runtime.clone();
746        runtime.block_on(async { self.pocket_ic.upload_blob(blob, compression).await })
747    }
748
749    /// Set stable memory of a canister. Optional GZIP compression can be used for reduced
750    /// data traffic.
751    #[instrument(skip(self, data), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string(), data_len = %data.len(), compression = ?compression))]
752    pub fn set_stable_memory(
753        &self,
754        canister_id: CanisterId,
755        data: Vec<u8>,
756        compression: BlobCompression,
757    ) -> Result<(), ClientError> {
758        let runtime = self.runtime.clone();
759        runtime.block_on(async {
760            self.pocket_ic
761                .set_stable_memory(canister_id, data, compression)
762                .await
763        })
764    }
765
766    /// Get stable memory of a canister.
767    #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string()))]
768    pub fn get_stable_memory(&self, canister_id: CanisterId) -> Result<Vec<u8>, ClientError> {
769        let runtime = self.runtime.clone();
770        runtime.block_on(async { self.pocket_ic.get_stable_memory(canister_id).await })
771    }
772
773    /// List all instances and their status.
774    #[instrument(ret)]
775    pub fn list_instances() -> Result<Vec<String>, ClientError> {
776        let runtime = tokio::runtime::Builder::new_current_thread()
777            .enable_all()
778            .build()?;
779        runtime.block_on(PocketIcAsync::list_instances())
780    }
781
782    /// Verify a canister signature.
783    #[instrument(skip_all, fields(instance_id=self.pocket_ic.instance_id))]
784    pub fn verify_canister_signature(
785        &self,
786        msg: Vec<u8>,
787        sig: Vec<u8>,
788        pubkey: Vec<u8>,
789        root_pubkey: Vec<u8>,
790    ) -> Result<(), ClientError> {
791        let runtime = self.runtime.clone();
792        runtime.block_on(async {
793            self.pocket_ic
794                .verify_canister_signature(msg, sig, pubkey, root_pubkey)
795                .await
796        })
797    }
798
799    /// Make the IC produce and progress by one block.
800    /// Note that multiple ticks might be necessary to observe
801    /// an expected effect, e.g., if the effect depends on
802    /// inter-canister calls or heartbeats.
803    #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id))]
804    pub fn tick(&self) -> Result<(), ClientError> {
805        let runtime = self.runtime.clone();
806        runtime.block_on(async { self.pocket_ic.tick().await })
807    }
808
809    /// Make the IC produce and progress by one block with custom
810    /// configs for the round.
811    #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id))]
812    pub fn tick_with_configs(&self, configs: TickConfigs) -> Result<(), ClientError> {
813        let runtime = self.runtime.clone();
814        runtime.block_on(async { self.pocket_ic.tick_with_configs(configs).await })
815    }
816
817    /// Configures the IC to make progress automatically,
818    /// i.e., periodically update the time of the IC
819    /// to the real time and execute rounds on the subnets.
820    /// Only returns after the certified time of the IC
821    /// has been updated for the first time.
822    /// Returns the URL at which `/api` requests
823    /// for this instance can be made.
824    #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id))]
825    pub fn auto_progress(&self) -> Result<Url, ClientError> {
826        let runtime = self.runtime.clone();
827        runtime.block_on(async { self.pocket_ic.auto_progress().await })
828    }
829
830    /// Returns whether automatic progress is enabled on the PocketIC instance.
831    #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id))]
832    pub fn auto_progress_enabled(&self) -> Result<bool, ClientError> {
833        let runtime = self.runtime.clone();
834        runtime.block_on(async { self.pocket_ic.auto_progress_enabled().await })
835    }
836
837    /// Stops automatic progress (see `auto_progress`) on the IC.
838    #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id))]
839    pub fn stop_progress(&self) -> Result<(), ClientError> {
840        let runtime = self.runtime.clone();
841        runtime.block_on(async { self.pocket_ic.stop_progress().await })
842    }
843
844    /// Returns the URL at which `/api` requests
845    /// for this instance can be made if the HTTP
846    /// gateway has been started.
847    pub fn url(&self) -> Option<Url> {
848        self.pocket_ic.url()
849    }
850
851    /// Creates an HTTP gateway for this PocketIC instance binding to `127.0.0.1`
852    /// and an optionally specified port (defaults to choosing an arbitrary unassigned port);
853    /// listening on `localhost`;
854    /// and configures the PocketIC instance to make progress automatically, i.e.,
855    /// periodically update the time of the PocketIC instance to the real time
856    /// and process messages on the PocketIC instance.
857    /// Only returns after the certified time of the PocketIC instance
858    /// has been updated for the first time.
859    /// Returns the URL at which `/api` requests
860    /// for this instance can be made.
861    #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id))]
862    pub fn make_live(&mut self, listen_at: Option<u16>) -> Result<Url, ClientError> {
863        let runtime = self.runtime.clone();
864        runtime.block_on(async { self.pocket_ic.make_live(listen_at).await })
865    }
866
867    /// Creates an HTTP gateway for this PocketIC instance binding
868    /// to an optionally specified IP address (defaults to `127.0.0.1`)
869    /// and port (defaults to choosing an arbitrary unassigned port);
870    /// listening on optionally specified domains (default to `localhost`);
871    /// and using an optionally specified TLS certificate (if provided, an HTTPS gateway is created)
872    /// and configures the PocketIC instance to make progress automatically, i.e.,
873    /// periodically update the time of the PocketIC instance to the real time
874    /// and process messages on the PocketIC instance.
875    /// Only returns after the certified time of the PocketIC instance
876    /// has been updated for the first time.
877    /// Returns the URL at which `/api` requests
878    /// for this instance can be made.
879    #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id))]
880    pub fn make_live_with_params(
881        &mut self,
882        ip_addr: Option<IpAddr>,
883        listen_at: Option<u16>,
884        domains: Option<Vec<String>>,
885        https_config: Option<HttpsConfig>,
886    ) -> Result<Url, ClientError> {
887        let runtime = self.runtime.clone();
888        runtime.block_on(async {
889            self.pocket_ic
890                .make_live_with_params(ip_addr, listen_at, domains, https_config)
891                .await
892        })
893    }
894
895    /// Stops auto progress (automatic time updates and round executions)
896    /// and the HTTP gateway for this IC instance.
897    #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id))]
898    pub fn stop_live(&mut self) -> Result<(), ClientError> {
899        let runtime = self.runtime.clone();
900        runtime.block_on(async { self.pocket_ic.stop_live().await })
901    }
902
903    /// Get the root key of this IC instance. Returns `None` if the IC has no NNS subnet.
904    #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id))]
905    pub fn root_key(&self) -> Result<Option<Vec<u8>>, ClientError> {
906        let runtime = self.runtime.clone();
907        runtime.block_on(async { self.pocket_ic.root_key().await })
908    }
909
910    /// Get the current time of the IC.
911    #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id))]
912    pub fn get_time(&self) -> Result<Time, ClientError> {
913        let runtime = self.runtime.clone();
914        runtime.block_on(async { self.pocket_ic.get_time().await })
915    }
916
917    /// Set the current time of the IC, on all subnets.
918    #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, time = ?time))]
919    pub fn set_time(&self, time: Time) -> Result<(), ClientError> {
920        let runtime = self.runtime.clone();
921        runtime.block_on(async { self.pocket_ic.set_time(time).await })
922    }
923
924    /// Set the current certified time of the IC, on all subnets.
925    #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, time = ?time))]
926    pub fn set_certified_time(&self, time: Time) -> Result<(), ClientError> {
927        let runtime = self.runtime.clone();
928        runtime.block_on(async { self.pocket_ic.set_certified_time(time).await })
929    }
930
931    /// Advance the time on the IC on all subnets by some nanoseconds.
932    #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, duration = ?duration))]
933    pub fn advance_time(&self, duration: Duration) -> Result<(), ClientError> {
934        let runtime = self.runtime.clone();
935        runtime.block_on(async { self.pocket_ic.advance_time(duration).await })
936    }
937
938    /// Get the controllers of a canister.
939    /// Returns a server error if the canister does not exist.
940    #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string()))]
941    pub fn get_controllers(&self, canister_id: CanisterId) -> Result<Vec<Principal>, ClientError> {
942        let runtime = self.runtime.clone();
943        runtime.block_on(async { self.pocket_ic.get_controllers(canister_id).await })
944    }
945
946    /// Get the current cycles balance of a canister.
947    #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string()))]
948    pub fn cycle_balance(&self, canister_id: CanisterId) -> Result<u128, ClientError> {
949        let runtime = self.runtime.clone();
950        runtime.block_on(async { self.pocket_ic.cycle_balance(canister_id).await })
951    }
952
953    /// Add cycles to a canister. Returns the new balance.
954    #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string(), amount = %amount))]
955    pub fn add_cycles(&self, canister_id: CanisterId, amount: u128) -> Result<u128, ClientError> {
956        let runtime = self.runtime.clone();
957        runtime.block_on(async { self.pocket_ic.add_cycles(canister_id, amount).await })
958    }
959
960    /// Submit an update call (without executing it immediately).
961    pub fn submit_call(
962        &self,
963        canister_id: CanisterId,
964        sender: Principal,
965        method: &str,
966        payload: Vec<u8>,
967    ) -> Result<RawMessageId, ClientError> {
968        let runtime = self.runtime.clone();
969        runtime.block_on(async {
970            self.pocket_ic
971                .submit_call(canister_id, sender, method, payload)
972                .await
973        })
974    }
975
976    /// Submit an update call with a provided effective principal (without executing it immediately).
977    pub fn submit_call_with_effective_principal(
978        &self,
979        canister_id: CanisterId,
980        effective_principal: RawEffectivePrincipal,
981        sender: Principal,
982        method: &str,
983        payload: Vec<u8>,
984    ) -> Result<RawMessageId, ClientError> {
985        let runtime = self.runtime.clone();
986        runtime.block_on(async {
987            self.pocket_ic
988                .submit_call_with_effective_principal(
989                    canister_id,
990                    effective_principal,
991                    sender,
992                    method,
993                    payload,
994                )
995                .await
996        })
997    }
998
999    /// Submit an update call with a provided effective principal and sender info (without executing it immediately).
1000    pub fn submit_call_with_effective_principal_and_sender_info(
1001        &self,
1002        canister_id: CanisterId,
1003        effective_principal: RawEffectivePrincipal,
1004        sender: Principal,
1005        method: &str,
1006        payload: Vec<u8>,
1007        sender_info: RawSenderInfo,
1008    ) -> Result<RawMessageId, ClientError> {
1009        let runtime = self.runtime.clone();
1010        runtime.block_on(async {
1011            self.pocket_ic
1012                .submit_call_with_effective_principal_and_sender_info(
1013                    canister_id,
1014                    effective_principal,
1015                    sender,
1016                    method,
1017                    payload,
1018                    sender_info,
1019                )
1020                .await
1021        })
1022    }
1023
1024    /// Submit an update call with sender info (without executing it immediately).
1025    pub fn submit_call_with_sender_info(
1026        &self,
1027        canister_id: CanisterId,
1028        sender: Principal,
1029        method: &str,
1030        payload: Vec<u8>,
1031        sender_info: RawSenderInfo,
1032    ) -> Result<RawMessageId, ClientError> {
1033        let runtime = self.runtime.clone();
1034        runtime.block_on(async {
1035            self.pocket_ic
1036                .submit_call_with_sender_info(canister_id, sender, method, payload, sender_info)
1037                .await
1038        })
1039    }
1040
1041    /// Await an update call submitted previously by `submit_call` or `submit_call_with_effective_principal`.
1042    pub fn await_call(&self, message_id: RawMessageId) -> Result<Vec<u8>, ClientError> {
1043        let runtime = self.runtime.clone();
1044        runtime.block_on(async { self.pocket_ic.await_call(message_id).await })
1045    }
1046
1047    /// Fetch the status of an update call submitted previously by `submit_call` or `submit_call_with_effective_principal`.
1048    /// Note that the status of the update call can only change if the PocketIC instance is in live mode
1049    /// or a round has been executed due to a separate PocketIC library call, e.g., `PocketIc::tick()`.
1050    pub fn ingress_status(
1051        &self,
1052        message_id: RawMessageId,
1053    ) -> Result<Option<Result<Vec<u8>, RejectResponse>>, ClientError> {
1054        let runtime = self.runtime.clone();
1055        runtime.block_on(async { self.pocket_ic.ingress_status(message_id).await })
1056    }
1057
1058    /// Fetch the status of an update call submitted previously by `submit_call` or `submit_call_with_effective_principal`.
1059    /// Note that the status of the update call can only change if the PocketIC instance is in live mode
1060    /// or a round has been executed due to a separate PocketIC library call, e.g., `PocketIc::tick()`.
1061    /// If the status of the update call is known, but the update call was submitted by a different caller, then an error is returned.
1062    pub fn ingress_status_as(
1063        &self,
1064        message_id: RawMessageId,
1065        caller: Principal,
1066    ) -> Result<IngressStatusResult, ClientError> {
1067        let runtime = self.runtime.clone();
1068        runtime.block_on(async { self.pocket_ic.ingress_status_as(message_id, caller).await })
1069    }
1070
1071    /// Await an update call submitted previously by `submit_call` or `submit_call_with_effective_principal`.
1072    /// Note that the status of the update call can only change if the PocketIC instance is in live mode
1073    /// or a round has been executed due to a separate PocketIC library call, e.g., `PocketIc::tick()`.
1074    pub fn await_call_no_ticks(&self, message_id: RawMessageId) -> Result<Vec<u8>, ClientError> {
1075        let runtime = self.runtime.clone();
1076        runtime.block_on(async { self.pocket_ic.await_call_no_ticks(message_id).await })
1077    }
1078
1079    /// Execute an update call on a canister.
1080    #[instrument(skip(self, payload), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string(), sender = %sender.to_string(), method = %method, payload_len = %payload.len()))]
1081    pub fn update_call(
1082        &self,
1083        canister_id: CanisterId,
1084        sender: Principal,
1085        method: &str,
1086        payload: Vec<u8>,
1087    ) -> Result<Vec<u8>, ClientError> {
1088        let runtime = self.runtime.clone();
1089        runtime.block_on(async {
1090            self.pocket_ic
1091                .update_call(canister_id, sender, method, payload)
1092                .await
1093        })
1094    }
1095
1096    /// Execute a query call on a canister.
1097    #[instrument(skip(self, payload), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string(), sender = %sender.to_string(), method = %method, payload_len = %payload.len()))]
1098    pub fn query_call(
1099        &self,
1100        canister_id: CanisterId,
1101        sender: Principal,
1102        method: &str,
1103        payload: Vec<u8>,
1104    ) -> Result<Vec<u8>, ClientError> {
1105        let runtime = self.runtime.clone();
1106        runtime.block_on(async {
1107            self.pocket_ic
1108                .query_call(canister_id, sender, method, payload)
1109                .await
1110        })
1111    }
1112
1113    /// Fetch canister logs via a query call to the management canister.
1114    pub fn fetch_canister_logs(
1115        &self,
1116        canister_id: CanisterId,
1117        sender: Principal,
1118    ) -> Result<Vec<CanisterLogRecord>, ClientError> {
1119        let runtime = self.runtime.clone();
1120        runtime.block_on(async {
1121            self.pocket_ic
1122                .fetch_canister_logs(canister_id, sender)
1123                .await
1124        })
1125    }
1126
1127    /// Request a canister's status.
1128    #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1129    pub fn canister_status(
1130        &self,
1131        canister_id: CanisterId,
1132        sender: Option<Principal>,
1133    ) -> Result<CanisterStatusResult, ClientError> {
1134        let runtime = self.runtime.clone();
1135        runtime.block_on(async { self.pocket_ic.canister_status(canister_id, sender).await })
1136    }
1137
1138    /// Create a canister with default settings as the anonymous principal.
1139    /// The canister is created with 100T cycles.
1140    #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id))]
1141    pub fn create_canister(&self) -> Result<CanisterId, ClientError> {
1142        let runtime = self.runtime.clone();
1143        runtime.block_on(async { self.pocket_ic.create_canister().await })
1144    }
1145
1146    /// Create a canister with optional custom settings and a sender.
1147    /// The canister is created with 100T cycles.
1148    #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id, settings = ?settings, sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1149    pub fn create_canister_with_settings(
1150        &self,
1151        sender: Option<Principal>,
1152        settings: Option<CanisterSettings>,
1153    ) -> Result<CanisterId, ClientError> {
1154        let runtime = self.runtime.clone();
1155        runtime.block_on(async {
1156            self.pocket_ic
1157                .create_canister_with_settings(sender, settings)
1158                .await
1159        })
1160    }
1161
1162    /// Creates a canister with a specific canister ID and optional custom settings.
1163    /// The canister is created with 100T cycles.
1164    /// Returns an error if the canister ID is already in use.
1165    /// Creates a new subnet if the canister ID is not contained in any of the subnets.
1166    ///
1167    /// The canister ID must be an IC mainnet canister ID that does not belong to the NNS or II subnet,
1168    /// otherwise the function might panic (for NNS and II canister IDs,
1169    /// the PocketIC instance should already be created with those subnets).
1170    #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id, sender = %sender.unwrap_or(Principal::anonymous()).to_string(), settings = ?settings, canister_id = %canister_id.to_string()))]
1171    pub fn create_canister_with_id(
1172        &self,
1173        sender: Option<Principal>,
1174        settings: Option<CanisterSettings>,
1175        canister_id: CanisterId,
1176    ) -> Result<CanisterId, ClientError> {
1177        let runtime = self.runtime.clone();
1178        runtime.block_on(async {
1179            self.pocket_ic
1180                .create_canister_with_id(sender, settings, canister_id)
1181                .await
1182        })
1183    }
1184
1185    /// Create a canister on a specific subnet with optional custom settings.
1186    /// The canister is created with 100T cycles.
1187    #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id, sender = %sender.unwrap_or(Principal::anonymous()).to_string(), settings = ?settings, subnet_id = %subnet_id.to_string()))]
1188    pub fn create_canister_on_subnet(
1189        &self,
1190        sender: Option<Principal>,
1191        settings: Option<CanisterSettings>,
1192        subnet_id: SubnetId,
1193    ) -> Result<CanisterId, ClientError> {
1194        let runtime = self.runtime.clone();
1195        runtime.block_on(async {
1196            self.pocket_ic
1197                .create_canister_on_subnet(sender, settings, subnet_id)
1198                .await
1199        })
1200    }
1201
1202    /// Create a canister with optional cycles, settings, and placement.
1203    /// The placement specifies either a target subnet or a specific canister ID.
1204    /// Defaults to 100T cycles if `params.cycles` is `None`.
1205    /// Returns an error if the specified canister ID is already in use.
1206    #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id, sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1207    pub fn create_canister_with_params(
1208        &self,
1209        sender: Option<Principal>,
1210        params: CreateCanisterParams,
1211    ) -> Result<CanisterId, ClientError> {
1212        let runtime = self.runtime.clone();
1213        runtime.block_on(async {
1214            self.pocket_ic
1215                .create_canister_with_params(sender, params)
1216                .await
1217        })
1218    }
1219
1220    /// Upload a WASM chunk to the WASM chunk store of a canister.
1221    /// Returns the WASM chunk hash.
1222    #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string(), sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1223    pub fn upload_chunk(
1224        &self,
1225        canister_id: CanisterId,
1226        sender: Option<Principal>,
1227        chunk: Vec<u8>,
1228    ) -> Result<Vec<u8>, ClientError> {
1229        let runtime = self.runtime.clone();
1230        runtime.block_on(async {
1231            self.pocket_ic
1232                .upload_chunk(canister_id, sender, chunk)
1233                .await
1234        })
1235    }
1236
1237    /// List WASM chunk hashes in the WASM chunk store of a canister.
1238    #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string(), sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1239    pub fn stored_chunks(
1240        &self,
1241        canister_id: CanisterId,
1242        sender: Option<Principal>,
1243    ) -> Result<Vec<Vec<u8>>, ClientError> {
1244        let runtime = self.runtime.clone();
1245        runtime.block_on(async { self.pocket_ic.stored_chunks(canister_id, sender).await })
1246    }
1247
1248    /// Clear the WASM chunk store of a canister.
1249    #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string(), sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1250    pub fn clear_chunk_store(
1251        &self,
1252        canister_id: CanisterId,
1253        sender: Option<Principal>,
1254    ) -> Result<(), ClientError> {
1255        let runtime = self.runtime.clone();
1256        runtime.block_on(async { self.pocket_ic.clear_chunk_store(canister_id, sender).await })
1257    }
1258
1259    /// Install a WASM module assembled from chunks on an existing canister.
1260    #[instrument(skip(self, mode, chunk_hashes_list, wasm_module_hash, arg), fields(instance_id=self.pocket_ic.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()))]
1261    pub fn install_chunked_canister(
1262        &self,
1263        canister_id: CanisterId,
1264        sender: Option<Principal>,
1265        mode: CanisterInstallMode,
1266        store_canister_id: CanisterId,
1267        chunk_hashes_list: Vec<Vec<u8>>,
1268        wasm_module_hash: Vec<u8>,
1269        arg: Vec<u8>,
1270    ) -> Result<(), ClientError> {
1271        let runtime = self.runtime.clone();
1272        runtime.block_on(async {
1273            self.pocket_ic
1274                .install_chunked_canister(
1275                    canister_id,
1276                    sender,
1277                    mode,
1278                    store_canister_id,
1279                    chunk_hashes_list,
1280                    wasm_module_hash,
1281                    arg,
1282                )
1283                .await
1284        })
1285    }
1286
1287    /// Install a WASM module on an existing canister.
1288    #[instrument(skip(self, wasm_module, arg), fields(instance_id=self.pocket_ic.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()))]
1289    pub fn install_canister(
1290        &self,
1291        canister_id: CanisterId,
1292        wasm_module: Vec<u8>,
1293        arg: Vec<u8>,
1294        sender: Option<Principal>,
1295    ) -> Result<(), ClientError> {
1296        let runtime = self.runtime.clone();
1297        runtime.block_on(async {
1298            self.pocket_ic
1299                .install_canister(canister_id, wasm_module, arg, sender)
1300                .await
1301        })
1302    }
1303
1304    /// Upgrade a canister with a new WASM module.
1305    #[instrument(skip(self, wasm_module, arg), fields(instance_id=self.pocket_ic.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()))]
1306    pub fn upgrade_canister(
1307        &self,
1308        canister_id: CanisterId,
1309        wasm_module: Vec<u8>,
1310        arg: Vec<u8>,
1311        sender: Option<Principal>,
1312    ) -> Result<(), ClientError> {
1313        let runtime = self.runtime.clone();
1314        runtime.block_on(async {
1315            self.pocket_ic
1316                .upgrade_canister(canister_id, wasm_module, arg, sender)
1317                .await
1318        })
1319    }
1320
1321    /// Upgrade a Motoko EOP canister with a new WASM module.
1322    #[instrument(skip(self, wasm_module, arg), fields(instance_id=self.pocket_ic.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()))]
1323    pub fn upgrade_eop_canister(
1324        &self,
1325        canister_id: CanisterId,
1326        wasm_module: Vec<u8>,
1327        arg: Vec<u8>,
1328        sender: Option<Principal>,
1329    ) -> Result<(), ClientError> {
1330        let runtime = self.runtime.clone();
1331        runtime.block_on(async {
1332            self.pocket_ic
1333                .upgrade_eop_canister(canister_id, wasm_module, arg, sender)
1334                .await
1335        })
1336    }
1337
1338    /// Reinstall a canister WASM module.
1339    #[instrument(skip(self, wasm_module, arg), fields(instance_id=self.pocket_ic.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()))]
1340    pub fn reinstall_canister(
1341        &self,
1342        canister_id: CanisterId,
1343        wasm_module: Vec<u8>,
1344        arg: Vec<u8>,
1345        sender: Option<Principal>,
1346    ) -> Result<(), ClientError> {
1347        let runtime = self.runtime.clone();
1348        runtime.block_on(async {
1349            self.pocket_ic
1350                .reinstall_canister(canister_id, wasm_module, arg, sender)
1351                .await
1352        })
1353    }
1354
1355    /// Uninstall a canister.
1356    #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string(), sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1357    pub fn uninstall_canister(
1358        &self,
1359        canister_id: CanisterId,
1360        sender: Option<Principal>,
1361    ) -> Result<(), ClientError> {
1362        let runtime = self.runtime.clone();
1363        runtime.block_on(async { self.pocket_ic.uninstall_canister(canister_id, sender).await })
1364    }
1365
1366    /// Take canister snapshot.
1367    #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string(), sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1368    pub fn take_canister_snapshot(
1369        &self,
1370        canister_id: CanisterId,
1371        sender: Option<Principal>,
1372        replace_snapshot: Option<Vec<u8>>,
1373    ) -> Result<Snapshot, ClientError> {
1374        let runtime = self.runtime.clone();
1375        runtime.block_on(async {
1376            self.pocket_ic
1377                .take_canister_snapshot(canister_id, sender, replace_snapshot)
1378                .await
1379        })
1380    }
1381
1382    /// Load canister snapshot.
1383    #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string(), sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1384    pub fn load_canister_snapshot(
1385        &self,
1386        canister_id: CanisterId,
1387        sender: Option<Principal>,
1388        snapshot_id: Vec<u8>,
1389    ) -> Result<(), ClientError> {
1390        let runtime = self.runtime.clone();
1391        runtime.block_on(async {
1392            self.pocket_ic
1393                .load_canister_snapshot(canister_id, sender, snapshot_id)
1394                .await
1395        })
1396    }
1397
1398    /// List canister snapshots.
1399    #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string(), sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1400    pub fn list_canister_snapshots(
1401        &self,
1402        canister_id: CanisterId,
1403        sender: Option<Principal>,
1404    ) -> Result<Vec<Snapshot>, ClientError> {
1405        let runtime = self.runtime.clone();
1406        runtime.block_on(async {
1407            self.pocket_ic
1408                .list_canister_snapshots(canister_id, sender)
1409                .await
1410        })
1411    }
1412
1413    /// Delete canister snapshot.
1414    #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string(), sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1415    pub fn delete_canister_snapshot(
1416        &self,
1417        canister_id: CanisterId,
1418        sender: Option<Principal>,
1419        snapshot_id: Vec<u8>,
1420    ) -> Result<(), ClientError> {
1421        let runtime = self.runtime.clone();
1422        runtime.block_on(async {
1423            self.pocket_ic
1424                .delete_canister_snapshot(canister_id, sender, snapshot_id)
1425                .await
1426        })
1427    }
1428
1429    /// Update canister settings.
1430    #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string(), sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1431    pub fn update_canister_settings(
1432        &self,
1433        canister_id: CanisterId,
1434        sender: Option<Principal>,
1435        settings: CanisterSettings,
1436    ) -> Result<(), ClientError> {
1437        let runtime = self.runtime.clone();
1438        runtime.block_on(async {
1439            self.pocket_ic
1440                .update_canister_settings(canister_id, sender, settings)
1441                .await
1442        })
1443    }
1444
1445    /// Set canister's controllers.
1446    #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string(), sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1447    pub fn set_controllers(
1448        &self,
1449        canister_id: CanisterId,
1450        sender: Option<Principal>,
1451        new_controllers: Vec<Principal>,
1452    ) -> Result<(), ClientError> {
1453        let runtime = self.runtime.clone();
1454        runtime.block_on(async {
1455            self.pocket_ic
1456                .set_controllers(canister_id, sender, new_controllers)
1457                .await
1458        })
1459    }
1460
1461    /// Start a canister.
1462    #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string(), sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1463    pub fn start_canister(
1464        &self,
1465        canister_id: CanisterId,
1466        sender: Option<Principal>,
1467    ) -> Result<(), ClientError> {
1468        let runtime = self.runtime.clone();
1469        runtime.block_on(async { self.pocket_ic.start_canister(canister_id, sender).await })
1470    }
1471
1472    /// Stop a canister.
1473    #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string(), sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1474    pub fn stop_canister(
1475        &self,
1476        canister_id: CanisterId,
1477        sender: Option<Principal>,
1478    ) -> Result<(), ClientError> {
1479        let runtime = self.runtime.clone();
1480        runtime.block_on(async { self.pocket_ic.stop_canister(canister_id, sender).await })
1481    }
1482
1483    /// Delete a canister.
1484    #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string(), sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1485    pub fn delete_canister(
1486        &self,
1487        canister_id: CanisterId,
1488        sender: Option<Principal>,
1489    ) -> Result<(), ClientError> {
1490        let runtime = self.runtime.clone();
1491        runtime.block_on(async { self.pocket_ic.delete_canister(canister_id, sender).await })
1492    }
1493
1494    /// Checks whether the provided canister exists.
1495    #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string()))]
1496    pub fn canister_exists(&self, canister_id: CanisterId) -> Result<bool, ClientError> {
1497        let runtime = self.runtime.clone();
1498        runtime.block_on(async { self.pocket_ic.canister_exists(canister_id).await })
1499    }
1500
1501    /// Deletes a subnet. Returns a server error if the subnet does not exist or is a named subnet.
1502    #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id, subnet_id = %subnet_id.to_string()))]
1503    pub fn delete_subnet(&self, subnet_id: SubnetId) -> Result<(), ClientError> {
1504        let runtime = self.runtime.clone();
1505        runtime.block_on(async { self.pocket_ic.delete_subnet(subnet_id).await })
1506    }
1507
1508    /// Returns the subnet ID of the canister if the canister exists.
1509    #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string()))]
1510    pub fn get_subnet(&self, canister_id: CanisterId) -> Result<Option<SubnetId>, ClientError> {
1511        let runtime = self.runtime.clone();
1512        runtime.block_on(async { self.pocket_ic.get_subnet(canister_id).await })
1513    }
1514
1515    /// Returns subnet metrics for a given subnet.
1516    #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id, subnet_id = %subnet_id.to_string()))]
1517    pub fn get_subnet_metrics(
1518        &self,
1519        subnet_id: Principal,
1520    ) -> Result<Option<SubnetMetrics>, ClientError> {
1521        let runtime = self.runtime.clone();
1522        runtime.block_on(async { self.pocket_ic.get_subnet_metrics(subnet_id).await })
1523    }
1524
1525    pub fn update_call_with_effective_principal(
1526        &self,
1527        canister_id: CanisterId,
1528        effective_principal: RawEffectivePrincipal,
1529        sender: Principal,
1530        method: &str,
1531        payload: Vec<u8>,
1532    ) -> Result<Vec<u8>, ClientError> {
1533        let runtime = self.runtime.clone();
1534        runtime.block_on(async {
1535            self.pocket_ic
1536                .update_call_with_effective_principal(
1537                    canister_id,
1538                    effective_principal,
1539                    sender,
1540                    method,
1541                    payload,
1542                )
1543                .await
1544        })
1545    }
1546
1547    /// Execute an update call with a provided effective principal and sender info on a canister.
1548    pub fn update_call_with_effective_principal_and_sender_info(
1549        &self,
1550        canister_id: CanisterId,
1551        effective_principal: RawEffectivePrincipal,
1552        sender: Principal,
1553        method: &str,
1554        payload: Vec<u8>,
1555        sender_info: RawSenderInfo,
1556    ) -> Result<Vec<u8>, ClientError> {
1557        let runtime = self.runtime.clone();
1558        runtime.block_on(async {
1559            self.pocket_ic
1560                .update_call_with_effective_principal_and_sender_info(
1561                    canister_id,
1562                    effective_principal,
1563                    sender,
1564                    method,
1565                    payload,
1566                    sender_info,
1567                )
1568                .await
1569        })
1570    }
1571
1572    /// Execute an update call with sender info on a canister.
1573    pub fn update_call_with_sender_info(
1574        &self,
1575        canister_id: CanisterId,
1576        sender: Principal,
1577        method: &str,
1578        payload: Vec<u8>,
1579        sender_info: RawSenderInfo,
1580    ) -> Result<Vec<u8>, ClientError> {
1581        let runtime = self.runtime.clone();
1582        runtime.block_on(async {
1583            self.pocket_ic
1584                .update_call_with_sender_info(canister_id, sender, method, payload, sender_info)
1585                .await
1586        })
1587    }
1588
1589    /// Execute a query call on a canister explicitly specifying an effective principal to route the request:
1590    /// this API is useful for making generic query calls (including management canister query calls) without using dedicated functions from this library
1591    /// (e.g., making generic query calls in dfx to a PocketIC instance).
1592    #[instrument(skip(self, payload), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string(), effective_principal = %effective_principal.to_string(), sender = %sender.to_string(), method = %method, payload_len = %payload.len()))]
1593    pub fn query_call_with_effective_principal(
1594        &self,
1595        canister_id: CanisterId,
1596        effective_principal: RawEffectivePrincipal,
1597        sender: Principal,
1598        method: &str,
1599        payload: Vec<u8>,
1600    ) -> Result<Vec<u8>, ClientError> {
1601        let runtime = self.runtime.clone();
1602        runtime.block_on(async {
1603            self.pocket_ic
1604                .query_call_with_effective_principal(
1605                    canister_id,
1606                    effective_principal,
1607                    sender,
1608                    method,
1609                    payload,
1610                )
1611                .await
1612        })
1613    }
1614
1615    /// Execute a query call with a provided effective principal and sender info on a canister.
1616    pub fn query_call_with_effective_principal_and_sender_info(
1617        &self,
1618        canister_id: CanisterId,
1619        effective_principal: RawEffectivePrincipal,
1620        sender: Principal,
1621        method: &str,
1622        payload: Vec<u8>,
1623        sender_info: RawSenderInfo,
1624    ) -> Result<Vec<u8>, ClientError> {
1625        let runtime = self.runtime.clone();
1626        runtime.block_on(async {
1627            self.pocket_ic
1628                .query_call_with_effective_principal_and_sender_info(
1629                    canister_id,
1630                    effective_principal,
1631                    sender,
1632                    method,
1633                    payload,
1634                    sender_info,
1635                )
1636                .await
1637        })
1638    }
1639
1640    /// Execute a query call with sender info on a canister.
1641    pub fn query_call_with_sender_info(
1642        &self,
1643        canister_id: CanisterId,
1644        sender: Principal,
1645        method: &str,
1646        payload: Vec<u8>,
1647        sender_info: RawSenderInfo,
1648    ) -> Result<Vec<u8>, ClientError> {
1649        let runtime = self.runtime.clone();
1650        runtime.block_on(async {
1651            self.pocket_ic
1652                .query_call_with_sender_info(canister_id, sender, method, payload, sender_info)
1653                .await
1654        })
1655    }
1656
1657    /// Get the pending canister HTTP outcalls.
1658    /// Note that an additional `PocketIc::tick` is necessary after a canister
1659    /// executes a message making a canister HTTP outcall for the HTTP outcall
1660    /// to be retrievable here.
1661    /// Note that, unless a PocketIC instance is in auto progress mode,
1662    /// a response to the pending canister HTTP outcalls
1663    /// must be produced by the test driver and passed on to the PocketIC instace
1664    /// using `PocketIc::mock_canister_http_response`, or, for a *flexible* outcall
1665    /// (`CanisterHttpReplication::Flexible`), using
1666    /// `PocketIc::mock_flexible_canister_http_response`.
1667    /// In auto progress mode, the PocketIC server produces a response for every
1668    /// pending canister HTTP outcall by actually making an HTTP request
1669    /// to the specified URL.
1670    #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id))]
1671    pub fn get_canister_http(&self) -> Result<Vec<CanisterHttpRequest>, ClientError> {
1672        let runtime = self.runtime.clone();
1673        runtime.block_on(async { self.pocket_ic.get_canister_http().await })
1674    }
1675
1676    /// Mock a response to a pending canister HTTP outcall: the same response for
1677    /// every node of the subnet, or one response per node if
1678    /// `MockCanisterHttpResponse::additional_responses` is non-empty. For a
1679    /// *flexible* outcall, whose committee nodes are answered individually, see
1680    /// `PocketIc::mock_flexible_canister_http_response`.
1681    #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id))]
1682    pub fn mock_canister_http_response(
1683        &self,
1684        mock_canister_http_response: MockCanisterHttpResponse,
1685    ) -> Result<(), ClientError> {
1686        let runtime = self.runtime.clone();
1687        runtime.block_on(async {
1688            self.pocket_ic
1689                .mock_canister_http_response(mock_canister_http_response)
1690                .await
1691        })
1692    }
1693
1694    /// Mock the responses of the committee nodes of a pending *flexible* canister
1695    /// HTTP outcall, i.e. one made through the `flexible_http_request` management
1696    /// canister endpoint.
1697    ///
1698    /// This takes at most one response per node of the outcall's committee (whose
1699    /// size is the `total_requests` of the outcall's `CanisterHttpReplication::Flexible`
1700    /// replication). Providing fewer responses than the committee size
1701    /// models the remaining committee nodes never responding: with at least
1702    /// `min_responses` successful ones among them the outcall still succeeds, and
1703    /// with fewer it stays pending until the time is advanced past its 60 second
1704    /// timeout, at which point it fails with a timeout error.
1705    ///
1706    /// All responses to an outcall must be provided in a single call: once any
1707    /// response to it has been mocked, the outcall no longer shows up in
1708    /// `PocketIc::get_canister_http` and further responses to it cannot be mocked.
1709    #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id))]
1710    pub fn mock_flexible_canister_http_response(
1711        &self,
1712        mock_flexible_canister_http_response: MockFlexibleCanisterHttpResponse,
1713    ) -> Result<(), ClientError> {
1714        let runtime = self.runtime.clone();
1715        runtime.block_on(async {
1716            self.pocket_ic
1717                .mock_flexible_canister_http_response(mock_flexible_canister_http_response)
1718                .await
1719        })
1720    }
1721
1722    /// Download a canister snapshot to a given snapshot directory.
1723    /// The sender must be a controller of the canister.
1724    /// The snapshot directory must be empty if it exists.
1725    #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id))]
1726    pub fn canister_snapshot_download(
1727        &self,
1728        canister_id: CanisterId,
1729        sender: Principal,
1730        snapshot_id: Vec<u8>,
1731        snapshot_dir: PathBuf,
1732    ) -> Result<(), ClientError> {
1733        let runtime = self.runtime.clone();
1734        runtime.block_on(async {
1735            self.pocket_ic
1736                .canister_snapshot_download(canister_id, sender, snapshot_id, snapshot_dir)
1737                .await
1738        })
1739    }
1740
1741    /// Upload a canister snapshot from a given snapshot directory.
1742    /// The sender must be a controller of the canister.
1743    /// Returns the snapshot ID of the uploaded snapshot.
1744    #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id))]
1745    pub fn canister_snapshot_upload(
1746        &self,
1747        canister_id: CanisterId,
1748        sender: Principal,
1749        replace_snapshot: Option<Vec<u8>>,
1750        snapshot_dir: PathBuf,
1751    ) -> Result<Vec<u8>, ClientError> {
1752        let runtime = self.runtime.clone();
1753        runtime.block_on(async {
1754            self.pocket_ic
1755                .canister_snapshot_upload(canister_id, sender, replace_snapshot, snapshot_dir)
1756                .await
1757        })
1758    }
1759}
1760
1761impl Drop for PocketIc {
1762    fn drop(&mut self) {
1763        if !self.pocket_ic.cleanup_attempted {
1764            self.runtime.block_on(async {
1765                if let Err(error) = self.pocket_ic.do_drop().await {
1766                    tracing::warn!("PocketIC drop failed: {error}");
1767                }
1768            });
1769        }
1770        if let Some(thread) = self.thread.take() {
1771            let _ = thread.join();
1772        }
1773    }
1774}
1775
1776/// Call a canister candid method, authenticated. The sender can be impersonated (i.e., the
1777/// signature is not verified).
1778/// PocketIC executes update calls synchronously, so there is no need to poll for the result.
1779pub fn call_candid_as<Input, Output>(
1780    env: &PocketIc,
1781    canister_id: CanisterId,
1782    effective_principal: RawEffectivePrincipal,
1783    sender: Principal,
1784    method: &str,
1785    input: Input,
1786) -> Result<Output, ClientError>
1787where
1788    Input: ArgumentEncoder,
1789    Output: for<'a> ArgumentDecoder<'a>,
1790{
1791    with_candid(input, |payload| {
1792        env.update_call_with_effective_principal(
1793            canister_id,
1794            effective_principal,
1795            sender,
1796            method,
1797            payload,
1798        )
1799    })
1800}
1801
1802/// Call a canister candid method, anonymous.
1803/// PocketIC executes update calls synchronously, so there is no need to poll for the result.
1804pub fn call_candid<Input, Output>(
1805    env: &PocketIc,
1806    canister_id: CanisterId,
1807    effective_principal: RawEffectivePrincipal,
1808    method: &str,
1809    input: Input,
1810) -> Result<Output, ClientError>
1811where
1812    Input: ArgumentEncoder,
1813    Output: for<'a> ArgumentDecoder<'a>,
1814{
1815    call_candid_as(
1816        env,
1817        canister_id,
1818        effective_principal,
1819        Principal::anonymous(),
1820        method,
1821        input,
1822    )
1823}
1824
1825/// Call a canister candid query method, anonymous.
1826pub fn query_candid<Input, Output>(
1827    env: &PocketIc,
1828    canister_id: CanisterId,
1829    method: &str,
1830    input: Input,
1831) -> Result<Output, ClientError>
1832where
1833    Input: ArgumentEncoder,
1834    Output: for<'a> ArgumentDecoder<'a>,
1835{
1836    query_candid_as(env, canister_id, Principal::anonymous(), method, input)
1837}
1838
1839/// Call a canister candid query method, authenticated. The sender can be impersonated (i.e., the
1840/// signature is not verified).
1841pub fn query_candid_as<Input, Output>(
1842    env: &PocketIc,
1843    canister_id: CanisterId,
1844    sender: Principal,
1845    method: &str,
1846    input: Input,
1847) -> Result<Output, ClientError>
1848where
1849    Input: ArgumentEncoder,
1850    Output: for<'a> ArgumentDecoder<'a>,
1851{
1852    with_candid(input, |bytes| {
1853        env.query_call(canister_id, sender, method, bytes)
1854    })
1855}
1856
1857/// Call a canister candid update method, anonymous.
1858pub fn update_candid<Input, Output>(
1859    env: &PocketIc,
1860    canister_id: CanisterId,
1861    method: &str,
1862    input: Input,
1863) -> Result<Output, ClientError>
1864where
1865    Input: ArgumentEncoder,
1866    Output: for<'a> ArgumentDecoder<'a>,
1867{
1868    update_candid_as(env, canister_id, Principal::anonymous(), method, input)
1869}
1870
1871/// Call a canister candid update method, authenticated. The sender can be impersonated (i.e., the
1872/// signature is not verified).
1873pub fn update_candid_as<Input, Output>(
1874    env: &PocketIc,
1875    canister_id: CanisterId,
1876    sender: Principal,
1877    method: &str,
1878    input: Input,
1879) -> Result<Output, ClientError>
1880where
1881    Input: ArgumentEncoder,
1882    Output: for<'a> ArgumentDecoder<'a>,
1883{
1884    with_candid(input, |bytes| {
1885        env.update_call(canister_id, sender, method, bytes)
1886    })
1887}
1888
1889/// A helper function that we use to implement both [`call_candid`] and
1890/// [`query_candid`].
1891pub fn with_candid<Input, Output>(
1892    input: Input,
1893    f: impl FnOnce(Vec<u8>) -> Result<Vec<u8>, ClientError>,
1894) -> Result<Output, ClientError>
1895where
1896    Input: ArgumentEncoder,
1897    Output: for<'a> ArgumentDecoder<'a>,
1898{
1899    let bytes = encode_args(input)?;
1900    let output = f(bytes)?;
1901    Ok(decode_args(&output)?)
1902}
1903
1904/// Error type for [`TryFrom<u64>`].
1905#[derive(Clone, Copy, Debug)]
1906pub enum TryFromError {
1907    ValueOutOfRange(u64),
1908}
1909
1910/// User-facing error codes.
1911///
1912/// The error codes are currently assigned using an HTTP-like
1913/// convention: the most significant digit is the corresponding reject
1914/// code and the rest is just a sequentially assigned two-digit
1915/// number.
1916#[derive(
1917    PartialOrd,
1918    Ord,
1919    Clone,
1920    Copy,
1921    Debug,
1922    PartialEq,
1923    Eq,
1924    Hash,
1925    Serialize,
1926    Deserialize,
1927    JsonSchema,
1928    EnumIter,
1929)]
1930pub enum ErrorCode {
1931    // 1xx -- `RejectCode::SysFatal`
1932    SubnetOversubscribed = 101,
1933    MaxNumberOfCanistersReached = 102,
1934    // 2xx -- `RejectCode::SysTransient`
1935    CanisterQueueFull = 201,
1936    IngressMessageTimeout = 202,
1937    CanisterQueueNotEmpty = 203,
1938    IngressHistoryFull = 204,
1939    CanisterIdAlreadyExists = 205,
1940    StopCanisterRequestTimeout = 206,
1941    CanisterOutOfCycles = 207,
1942    CertifiedStateUnavailable = 208,
1943    CanisterInstallCodeRateLimited = 209,
1944    CanisterHeapDeltaRateLimited = 210,
1945    SubnetCoolingDown = 211,
1946    // 3xx -- `RejectCode::DestinationInvalid`
1947    CanisterNotFound = 301,
1948    CanisterSnapshotNotFound = 305,
1949    // 4xx -- `RejectCode::CanisterReject`
1950    InsufficientMemoryAllocation = 402,
1951    InsufficientCyclesForCreateCanister = 403,
1952    SubnetNotFound = 404,
1953    CanisterNotHostedBySubnet = 405,
1954    CanisterRejectedMessage = 406,
1955    UnknownManagementMessage = 407,
1956    InvalidManagementPayload = 408,
1957    CanisterSnapshotImmutable = 409,
1958    InvalidSubnetAdmin = 410,
1959    // 5xx -- `RejectCode::CanisterError`
1960    CanisterTrapped = 502,
1961    CanisterCalledTrap = 503,
1962    CanisterContractViolation = 504,
1963    CanisterInvalidWasm = 505,
1964    CanisterDidNotReply = 506,
1965    CanisterOutOfMemory = 507,
1966    CanisterStopped = 508,
1967    CanisterStopping = 509,
1968    CanisterNotStopped = 510,
1969    CanisterStoppingCancelled = 511,
1970    CanisterInvalidController = 512,
1971    CanisterFunctionNotFound = 513,
1972    CanisterNonEmpty = 514,
1973    QueryCallGraphLoopDetected = 517,
1974    InsufficientCyclesInCall = 520,
1975    CanisterWasmEngineError = 521,
1976    CanisterInstructionLimitExceeded = 522,
1977    CanisterMemoryAccessLimitExceeded = 524,
1978    QueryCallGraphTooDeep = 525,
1979    QueryCallGraphTotalInstructionLimitExceeded = 526,
1980    CompositeQueryCalledInReplicatedMode = 527,
1981    QueryTimeLimitExceeded = 528,
1982    QueryCallGraphInternal = 529,
1983    InsufficientCyclesInComputeAllocation = 530,
1984    InsufficientCyclesInMemoryAllocation = 531,
1985    InsufficientCyclesInMemoryGrow = 532,
1986    ReservedCyclesLimitExceededInMemoryAllocation = 533,
1987    ReservedCyclesLimitExceededInMemoryGrow = 534,
1988    InsufficientCyclesInMessageMemoryGrow = 535,
1989    CanisterMethodNotFound = 536,
1990    CanisterWasmModuleNotFound = 537,
1991    CanisterAlreadyInstalled = 538,
1992    CanisterWasmMemoryLimitExceeded = 539,
1993    ReservedCyclesLimitIsTooLow = 540,
1994    CanisterInvalidControllerOrSubnetAdmin = 541,
1995    CanisterStatusAccessDenied = 542,
1996    // 6xx -- `RejectCode::SysUnknown`
1997    DeadlineExpired = 601,
1998    ResponseDropped = 602,
1999}
2000
2001impl TryFrom<u64> for ErrorCode {
2002    type Error = TryFromError;
2003    fn try_from(err: u64) -> Result<ErrorCode, Self::Error> {
2004        match err {
2005            // 1xx -- `RejectCode::SysFatal`
2006            101 => Ok(ErrorCode::SubnetOversubscribed),
2007            102 => Ok(ErrorCode::MaxNumberOfCanistersReached),
2008            // 2xx -- `RejectCode::SysTransient`
2009            201 => Ok(ErrorCode::CanisterQueueFull),
2010            202 => Ok(ErrorCode::IngressMessageTimeout),
2011            203 => Ok(ErrorCode::CanisterQueueNotEmpty),
2012            204 => Ok(ErrorCode::IngressHistoryFull),
2013            205 => Ok(ErrorCode::CanisterIdAlreadyExists),
2014            206 => Ok(ErrorCode::StopCanisterRequestTimeout),
2015            207 => Ok(ErrorCode::CanisterOutOfCycles),
2016            208 => Ok(ErrorCode::CertifiedStateUnavailable),
2017            209 => Ok(ErrorCode::CanisterInstallCodeRateLimited),
2018            210 => Ok(ErrorCode::CanisterHeapDeltaRateLimited),
2019            211 => Ok(ErrorCode::SubnetCoolingDown),
2020            // 3xx -- `RejectCode::DestinationInvalid`
2021            301 => Ok(ErrorCode::CanisterNotFound),
2022            305 => Ok(ErrorCode::CanisterSnapshotNotFound),
2023            // 4xx -- `RejectCode::CanisterReject`
2024            402 => Ok(ErrorCode::InsufficientMemoryAllocation),
2025            403 => Ok(ErrorCode::InsufficientCyclesForCreateCanister),
2026            404 => Ok(ErrorCode::SubnetNotFound),
2027            405 => Ok(ErrorCode::CanisterNotHostedBySubnet),
2028            406 => Ok(ErrorCode::CanisterRejectedMessage),
2029            407 => Ok(ErrorCode::UnknownManagementMessage),
2030            408 => Ok(ErrorCode::InvalidManagementPayload),
2031            409 => Ok(ErrorCode::CanisterSnapshotImmutable),
2032            410 => Ok(ErrorCode::InvalidSubnetAdmin),
2033            // 5xx -- `RejectCode::CanisterError`
2034            502 => Ok(ErrorCode::CanisterTrapped),
2035            503 => Ok(ErrorCode::CanisterCalledTrap),
2036            504 => Ok(ErrorCode::CanisterContractViolation),
2037            505 => Ok(ErrorCode::CanisterInvalidWasm),
2038            506 => Ok(ErrorCode::CanisterDidNotReply),
2039            507 => Ok(ErrorCode::CanisterOutOfMemory),
2040            508 => Ok(ErrorCode::CanisterStopped),
2041            509 => Ok(ErrorCode::CanisterStopping),
2042            510 => Ok(ErrorCode::CanisterNotStopped),
2043            511 => Ok(ErrorCode::CanisterStoppingCancelled),
2044            512 => Ok(ErrorCode::CanisterInvalidController),
2045            513 => Ok(ErrorCode::CanisterFunctionNotFound),
2046            514 => Ok(ErrorCode::CanisterNonEmpty),
2047            517 => Ok(ErrorCode::QueryCallGraphLoopDetected),
2048            520 => Ok(ErrorCode::InsufficientCyclesInCall),
2049            521 => Ok(ErrorCode::CanisterWasmEngineError),
2050            522 => Ok(ErrorCode::CanisterInstructionLimitExceeded),
2051            524 => Ok(ErrorCode::CanisterMemoryAccessLimitExceeded),
2052            525 => Ok(ErrorCode::QueryCallGraphTooDeep),
2053            526 => Ok(ErrorCode::QueryCallGraphTotalInstructionLimitExceeded),
2054            527 => Ok(ErrorCode::CompositeQueryCalledInReplicatedMode),
2055            528 => Ok(ErrorCode::QueryTimeLimitExceeded),
2056            529 => Ok(ErrorCode::QueryCallGraphInternal),
2057            530 => Ok(ErrorCode::InsufficientCyclesInComputeAllocation),
2058            531 => Ok(ErrorCode::InsufficientCyclesInMemoryAllocation),
2059            532 => Ok(ErrorCode::InsufficientCyclesInMemoryGrow),
2060            533 => Ok(ErrorCode::ReservedCyclesLimitExceededInMemoryAllocation),
2061            534 => Ok(ErrorCode::ReservedCyclesLimitExceededInMemoryGrow),
2062            535 => Ok(ErrorCode::InsufficientCyclesInMessageMemoryGrow),
2063            536 => Ok(ErrorCode::CanisterMethodNotFound),
2064            537 => Ok(ErrorCode::CanisterWasmModuleNotFound),
2065            538 => Ok(ErrorCode::CanisterAlreadyInstalled),
2066            539 => Ok(ErrorCode::CanisterWasmMemoryLimitExceeded),
2067            540 => Ok(ErrorCode::ReservedCyclesLimitIsTooLow),
2068            541 => Ok(ErrorCode::CanisterInvalidControllerOrSubnetAdmin),
2069            542 => Ok(ErrorCode::CanisterStatusAccessDenied),
2070            // 6xx -- `RejectCode::SysUnknown`
2071            601 => Ok(ErrorCode::DeadlineExpired),
2072            602 => Ok(ErrorCode::ResponseDropped),
2073            _ => Err(TryFromError::ValueOutOfRange(err)),
2074        }
2075    }
2076}
2077
2078impl std::fmt::Display for ErrorCode {
2079    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2080        // E.g. "IC0301"
2081        write!(f, "IC{:04}", *self as i32)
2082    }
2083}
2084
2085/// User-facing reject codes.
2086///
2087/// They can be derived from the most significant digit of the
2088/// corresponding error code.
2089#[derive(
2090    PartialOrd,
2091    Ord,
2092    Clone,
2093    Copy,
2094    Debug,
2095    PartialEq,
2096    Eq,
2097    Hash,
2098    Serialize,
2099    Deserialize,
2100    JsonSchema,
2101    EnumIter,
2102)]
2103pub enum RejectCode {
2104    SysFatal = 1,
2105    SysTransient = 2,
2106    DestinationInvalid = 3,
2107    CanisterReject = 4,
2108    CanisterError = 5,
2109    SysUnknown = 6,
2110}
2111
2112impl TryFrom<u64> for RejectCode {
2113    type Error = TryFromError;
2114    fn try_from(err: u64) -> Result<RejectCode, Self::Error> {
2115        match err {
2116            1 => Ok(RejectCode::SysFatal),
2117            2 => Ok(RejectCode::SysTransient),
2118            3 => Ok(RejectCode::DestinationInvalid),
2119            4 => Ok(RejectCode::CanisterReject),
2120            5 => Ok(RejectCode::CanisterError),
2121            6 => Ok(RejectCode::SysUnknown),
2122            _ => Err(TryFromError::ValueOutOfRange(err)),
2123        }
2124    }
2125}
2126
2127/// User-facing type describing an unsuccessful (also called reject) call response.
2128#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
2129pub struct RejectResponse {
2130    pub reject_code: RejectCode,
2131    pub reject_message: String,
2132    pub error_code: ErrorCode,
2133    pub certified: bool,
2134}
2135
2136impl std::fmt::Display for RejectResponse {
2137    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2138        // Follows [agent-rs](https://github.com/dfinity/agent-rs/blob/a651dbbe69e61d4e8508c144cd60cfa3118eeb3a/ic-agent/src/agent/agent_error.rs#L54)
2139        write!(
2140            f,
2141            "PocketIC returned a rejection error: reject code {:?}, reject message {}, error code {:?}",
2142            self.reject_code, self.reject_message, self.error_code
2143        )
2144    }
2145}
2146
2147/// This enum describes the result of retrieving ingress status.
2148/// The `IngressStatusResult::Forbidden` variant is produced
2149/// if an optional caller is provided and a corresponding read state request
2150/// for the status of the same update call signed by that specified caller
2151/// was rejected because the update call was submitted by a different caller.
2152#[derive(Debug, Serialize, Deserialize)]
2153pub enum IngressStatusResult {
2154    NotAvailable,
2155    Forbidden(String),
2156    Success(Result<Vec<u8>, RejectResponse>),
2157}
2158
2159#[derive(Clone, Debug, Default)]
2160pub struct TickConfigs {
2161    pub blockmakers: Option<Vec<SubnetBlockmakers>>,
2162}
2163
2164impl From<TickConfigs> for RawTickConfigs {
2165    fn from(tick_configs: TickConfigs) -> Self {
2166        Self {
2167            blockmakers: tick_configs.blockmakers.map(|blockmakers| {
2168                blockmakers
2169                    .into_iter()
2170                    .map(|blockmaker| blockmaker.into())
2171                    .collect()
2172            }),
2173        }
2174    }
2175}
2176
2177#[derive(Clone, Debug)]
2178pub struct SubnetBlockmakers {
2179    pub subnet: Principal,
2180    pub blockmaker: Principal,
2181    pub failed_blockmakers: Vec<Principal>,
2182}
2183
2184impl From<SubnetBlockmakers> for RawSubnetBlockmakers {
2185    fn from(blockmaker: SubnetBlockmakers) -> Self {
2186        Self {
2187            subnet: blockmaker.subnet.into(),
2188            blockmaker: blockmaker.blockmaker.into(),
2189            failed_blockmakers: blockmaker
2190                .failed_blockmakers
2191                .into_iter()
2192                .map(|p| p.into())
2193                .collect(),
2194        }
2195    }
2196}
2197
2198/// Converts an absolute Windows path with a drive prefix (e.g. `C:\Users\x\y`)
2199/// to the path under which WSL mounts it (e.g. `/mnt/c/Users/x/y`).
2200///
2201/// The drive letter is lower-cased and backslashes are replaced by forward slashes.
2202/// A verbatim prefix (`\\?\`), as produced by `std::fs::canonicalize` on Windows,
2203/// is stripped. Paths without a drive prefix (UNC paths, relative paths, ...) are
2204/// rejected.
2205#[cfg_attr(
2206    all(not(windows), not(test)),
2207    expect(
2208        dead_code,
2209        reason = "the retained WSL converter is used by Windows implementations"
2210    )
2211)]
2212fn windows_to_wsl(path: &str) -> Result<String, String> {
2213    let path = path.strip_prefix(r"\\?\").unwrap_or(path);
2214    let mut chars = path.chars();
2215    let drive = match (chars.next(), chars.next()) {
2216        (Some(drive), Some(':')) if drive.is_ascii_alphabetic() => drive.to_ascii_lowercase(),
2217        _ => {
2218            return Err(format!(
2219                "`{path}` does not start with a drive prefix such as `C:`"
2220            ));
2221        }
2222    };
2223    let rest = chars.as_str();
2224    if !rest.is_empty() && !rest.starts_with(['\\', '/']) {
2225        return Err(format!("`{path}` is a drive-relative path"));
2226    }
2227    Ok(format!("/mnt/{drive}{}", rest.replace('\\', "/")))
2228}
2229
2230#[cfg(windows)]
2231fn wsl_path(path: &PathBuf, desc: &str) -> String {
2232    windows_to_wsl(
2233        path.as_os_str()
2234            .to_str()
2235            .unwrap_or_else(|| panic!("Could not convert {} path ({:?}) to String", desc, path)),
2236    )
2237    .unwrap_or_else(|e| {
2238        panic!(
2239            "Could not convert {} path ({:?}) to WSL path: {:?}",
2240            desc, path, e
2241        )
2242    })
2243}
2244
2245#[cfg(windows)]
2246static WSL_WARM_UP: Once = Once::new();
2247
2248#[cfg(windows)]
2249fn warm_up_wsl() {
2250    WSL_WARM_UP.call_once(|| {
2251        let output = Command::new("wsl")
2252            .arg("bash")
2253            .arg("-c")
2254            .arg("true")
2255            .output()
2256            .expect("Failed to warm up WSL");
2257        if !output.status.success() {
2258            panic!(
2259                "Failed to warm up WSL.\nStatus: {}\nStdout: {}\nStderr: {}",
2260                output.status,
2261                String::from_utf8_lossy(&output.stdout),
2262                String::from_utf8_lossy(&output.stderr),
2263            );
2264        }
2265    });
2266}
2267
2268#[cfg(windows)]
2269fn pocket_ic_server_cmd(bin_path: &PathBuf) -> Command {
2270    warm_up_wsl();
2271    let mut cmd = Command::new("wsl");
2272    cmd.arg(wsl_path(bin_path, "PocketIC binary"));
2273    cmd
2274}
2275
2276#[cfg(not(windows))]
2277fn pocket_ic_server_cmd(bin_path: &PathBuf) -> Command {
2278    Command::new(bin_path)
2279}
2280
2281fn check_pocketic_server_version(version_line: &str) -> Result<(), String> {
2282    let unexpected_version = format!(
2283        "Unexpected PocketIC server version: got `{version_line}`; expected `{POCKET_IC_SERVER_NAME} x.y.z`."
2284    );
2285    let Some((pocket_ic_server, version)) = version_line.split_once(' ') else {
2286        return Err(unexpected_version);
2287    };
2288    if pocket_ic_server != POCKET_IC_SERVER_NAME {
2289        return Err(unexpected_version);
2290    }
2291    let req = VersionReq::parse(&format!(">={MIN_SERVER_VERSION},<{MAX_SERVER_VERSION}")).unwrap();
2292    let version = Version::parse(version)
2293        .map_err(|e| format!("Failed to parse PocketIC server version: {e}"))?;
2294    if !req.matches(&version) {
2295        return Err(format!(
2296            "Incompatible PocketIC server version: got {version}; expected {req}."
2297        ));
2298    }
2299
2300    Ok(())
2301}
2302
2303fn get_and_check_pocketic_server_version(server_binary: &PathBuf) -> Result<(), String> {
2304    let mut cmd = pocket_ic_server_cmd(server_binary);
2305    cmd.arg("--version");
2306    let output = cmd.output().map_err(|e| e.to_string())?;
2307    if !output.status.success() {
2308        return Err(format!(
2309            "PocketIC server failed to print its version.\nStatus: {}\nStdout: {}\nStderr: {}",
2310            output.status,
2311            String::from_utf8_lossy(&output.stdout),
2312            String::from_utf8_lossy(&output.stderr),
2313        ));
2314    }
2315    let version_str = String::from_utf8(output.stdout)
2316        .map_err(|e| format!("Failed to parse PocketIC server version: {e}."))?;
2317    let version_line = version_str.trim_end_matches('\n');
2318    check_pocketic_server_version(version_line)
2319}
2320
2321async fn download_pocketic_server(
2322    server_url: String,
2323    mut out: std::fs::File,
2324) -> Result<(), ClientError> {
2325    let url = Url::parse(&server_url).map_err(|e| ClientError::Protocol(e.to_string()))?;
2326    let client = reqwest::Client::new();
2327    let mut exchange = http::Exchange::new(&url, Some(DEFAULT_MAX_REQUEST_TIME_MS));
2328    let (status, bytes) = exchange.bytes(&client, client.get(url.clone())).await?;
2329    if !status.is_success() {
2330        return Err(exchange.server_error(status, &bytes));
2331    }
2332    let mut gz = GzDecoder::new(&bytes[..]);
2333    std::io::copy(&mut gz, &mut out)?;
2334    Ok(())
2335}
2336
2337#[derive(Default)]
2338pub struct StartServerParams {
2339    pub server_binary: Option<PathBuf>,
2340    /// Reuse an existing PocketIC server spawned by this process.
2341    pub reuse: bool,
2342    /// TTL for the PocketIC server.
2343    /// The server stops gracefully if no request has been received for the duration of its TTL
2344    /// after the last request finished and if there are no more pending requests.
2345    /// A default value of TTL is used if no `ttl` is specified here.
2346    /// Note: The TTL might not be overriden if the same test process sets `reuse` to `true`
2347    /// and passes different values of `ttl`.
2348    pub ttl: Option<Duration>,
2349    /// Hard TTL for the PocketIC server.
2350    /// The server stops with a hard exit after the duration of its hard TTL
2351    /// since its launch.
2352    /// If no `hard_ttl` is specified here, then the PocketIC server
2353    /// does not use any default hard TTL.
2354    /// Note: The hard TTL might not be overriden if the same test process sets `reuse` to `true`
2355    /// and passes different values of `hard_ttl`.
2356    pub hard_ttl: Option<Duration>,
2357}
2358
2359/// Attempt to start a new PocketIC server.
2360pub async fn start_server(params: StartServerParams) -> Result<(Child, Url), ClientError> {
2361    let default_bin_dir =
2362        std::env::temp_dir().join(format!("{POCKET_IC_SERVER_NAME}-{LATEST_SERVER_VERSION}"));
2363    let default_bin_path = default_bin_dir.join("pocket-ic");
2364    let bin_path_provided =
2365        params.server_binary.is_some() || std::env::var_os("POCKET_IC_BIN").is_some();
2366    let mut bin_path: PathBuf = params.server_binary.unwrap_or_else(|| {
2367        std::env::var_os("POCKET_IC_BIN")
2368            .unwrap_or_else(|| default_bin_path.clone().into())
2369            .into()
2370    });
2371
2372    if let Err(e) = get_and_check_pocketic_server_version(&bin_path) {
2373        if bin_path_provided {
2374            return Err(ClientError::Protocol(format!(
2375                "Failed to validate PocketIC server binary {}: {e}",
2376                bin_path.display()
2377            )));
2378        }
2379        bin_path = default_bin_path.clone();
2380        std::fs::create_dir_all(&default_bin_dir)?;
2381        let mut options = OpenOptions::new();
2382        options.write(true).create_new(true);
2383        #[cfg(unix)]
2384        options.mode(0o777);
2385        match options.open(&default_bin_path) {
2386            Ok(out) => {
2387                #[cfg(target_os = "macos")]
2388                let os = "darwin";
2389                #[cfg(not(target_os = "macos"))]
2390                let os = "linux";
2391                #[cfg(target_arch = "aarch64")]
2392                let arch = "arm64";
2393                #[cfg(not(target_arch = "aarch64"))]
2394                let arch = "x86_64";
2395                let server_url = format!(
2396                    "https://github.com/dfinity/pocketic/releases/download/{LATEST_SERVER_VERSION}/pocket-ic-{arch}-{os}.gz"
2397                );
2398                println!(
2399                    "Failed to validate PocketIC server binary `{}`: `{}`. Going to download PocketIC server {} from {} to the local path {}. To avoid downloads during test execution, please specify the path to the (ungzipped and executable) PocketIC server {} using the function `PocketIcBuilder::with_server_binary` or using the `POCKET_IC_BIN` environment variable.",
2400                    bin_path.display(),
2401                    e,
2402                    LATEST_SERVER_VERSION,
2403                    server_url,
2404                    default_bin_path.display(),
2405                    LATEST_SERVER_VERSION
2406                );
2407                if let Err(e) = download_pocketic_server(server_url, out).await {
2408                    let _ = std::fs::remove_file(default_bin_path);
2409                    return Err(e);
2410                }
2411            }
2412            _ => {
2413                // PocketIC server has already been created by another test: wait until it's fully downloaded.
2414                let start = std::time::Instant::now();
2415                loop {
2416                    if get_and_check_pocketic_server_version(&default_bin_path).is_ok() {
2417                        break;
2418                    }
2419                    if start.elapsed() > std::time::Duration::from_secs(60) {
2420                        let _ = std::fs::remove_file(&default_bin_path);
2421                        return Err(ClientError::Protocol(
2422                            "Timed out waiting for PocketIC server download".into(),
2423                        ));
2424                    }
2425                    tokio::time::sleep(Duration::from_millis(100)).await;
2426                }
2427            }
2428        }
2429    }
2430
2431    let port_file_path = if params.reuse {
2432        // We use the test driver's process ID to share the PocketIC server between multiple tests
2433        // launched by the same test driver.
2434        let test_driver_pid = std::process::id();
2435        std::env::temp_dir().join(format!("pocket_ic_{test_driver_pid}.port"))
2436    } else {
2437        NamedTempFile::new()?.into_temp_path().to_path_buf()
2438    };
2439    let mut cmd = pocket_ic_server_cmd(&bin_path);
2440    if let Some(ttl) = params.ttl {
2441        cmd.arg("--ttl").arg(ttl.as_secs().to_string());
2442    }
2443    if let Some(hard_ttl) = params.hard_ttl {
2444        cmd.arg("--hard-ttl").arg(hard_ttl.as_secs().to_string());
2445    }
2446    cmd.arg("--port-file");
2447    #[cfg(windows)]
2448    cmd.arg(wsl_path(&port_file_path, "PocketIC port file"));
2449    #[cfg(not(windows))]
2450    cmd.arg(port_file_path.clone());
2451    if let Ok(mute_server) = std::env::var("POCKET_IC_MUTE_SERVER")
2452        && !mute_server.is_empty()
2453    {
2454        cmd.stdout(std::process::Stdio::null());
2455        cmd.stderr(std::process::Stdio::null());
2456    }
2457
2458    // Start the server in the background so that it doesn't receive signals such as CTRL^C
2459    // from the foreground terminal.
2460    #[cfg(unix)]
2461    {
2462        use std::os::unix::process::CommandExt;
2463        cmd.process_group(0);
2464    }
2465
2466    // TODO: SDK-1936
2467    let mut child = cmd.spawn()?;
2468    let startup_deadline = std::time::Instant::now() + Duration::from_secs(60);
2469
2470    loop {
2471        if let Ok(port_string) = std::fs::read_to_string(port_file_path.clone())
2472            && port_string.contains("\n")
2473        {
2474            let port: u16 = port_string
2475                .trim_end()
2476                .parse()
2477                .expect("Failed to parse port to number");
2478            return Ok((
2479                child,
2480                Url::parse(&format!("http://{LOCALHOST}:{port}/")).unwrap(),
2481            ));
2482        }
2483        if std::time::Instant::now() >= startup_deadline {
2484            let _ = child.kill();
2485            let _ = child.wait();
2486            return Err(ClientError::Protocol(
2487                "Timed out waiting for PocketIC server port".into(),
2488            ));
2489        }
2490        tokio::time::sleep(Duration::from_millis(20)).await;
2491    }
2492}
2493
2494/// Retrieves a default effective canister id for canister creation on a PocketIC instance
2495/// characterized by:
2496///  - a PocketIC instance URL of the form `http://<ip>:<port>/instances/<instance_id>`;
2497///  - a PocketIC HTTP gateway URL of the form `http://<ip>:port` for a PocketIC instance.
2498///
2499/// Returns an error if the PocketIC instance topology could not be fetched or parsed, e.g.,
2500/// because the given URL points to a replica (i.e., does not meet any of the above two properties).
2501pub fn get_default_effective_canister_id(pocket_ic_url: String) -> Result<Principal, ClientError> {
2502    let runtime = Runtime::new()?;
2503    runtime
2504        .block_on(crate::pocket_ic::nonblocking::get_default_effective_canister_id(pocket_ic_url))
2505}
2506
2507pub fn copy_dir(
2508    src: impl AsRef<std::path::Path>,
2509    dst: impl AsRef<std::path::Path>,
2510) -> std::io::Result<()> {
2511    std::fs::create_dir_all(&dst)?;
2512    for entry in std::fs::read_dir(src)? {
2513        let entry = entry?;
2514        let ty = entry.file_type()?;
2515        if ty.is_dir() {
2516            copy_dir(entry.path(), dst.as_ref().join(entry.file_name()))?;
2517        } else {
2518            std::fs::copy(entry.path(), dst.as_ref().join(entry.file_name()))?;
2519        }
2520    }
2521    Ok(())
2522}
2523
2524#[cfg(test)]
2525mod tests;