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)]
9use crate::pocket_ic::{
66 common::rest::{
67 AutoProgressConfig, BlobCompression, BlobId, CanisterHttpRequest, ExtendedSubnetConfigSet,
68 HttpsConfig, IcpConfig, IcpFeatures, InitialTime, InstanceHttpGatewayConfig, InstanceId,
69 MockCanisterHttpResponse, MockFlexibleCanisterHttpResponse, RawEffectivePrincipal,
70 RawMessageId, RawSenderInfo, RawSubnetBlockmakers, RawTickConfigs, RawTime, SubnetId,
71 SubnetKind, SubnetSpec, Topology,
72 },
73 nonblocking::PocketIc as PocketIcAsync,
74};
75use candid::{
76 Principal, decode_args, encode_args,
77 utils::{ArgumentDecoder, ArgumentEncoder},
78};
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 serde::{Deserialize, Serialize};
87use slog::Level;
88use std::{
89 net::{IpAddr, SocketAddr},
90 path::PathBuf,
91 sync::mpsc::channel,
92 thread,
93 thread::JoinHandle,
94 time::{Duration, SystemTime, UNIX_EPOCH},
95};
96use strum_macros::EnumIter;
97use tempfile::TempDir;
98use tokio::runtime::Runtime;
99use tracing::{instrument, warn};
100
101mod error;
102mod http;
103pub use error::{ClientError, Operation, RequestContext};
104pub mod common;
105pub mod nonblocking;
106pub mod snapshot;
107
108pub const LATEST_SERVER_VERSION: &str = "16.1.0";
110
111const DEFAULT_MAX_REQUEST_TIME_MS: u64 = 300_000;
113
114enum PocketIcStateKind {
115 StateDir(PathBuf),
117 TempDir(TempDir),
123}
124
125pub struct PocketIcState {
126 state: PocketIcStateKind,
127}
128
129impl PocketIcState {
130 #[expect(
131 clippy::new_without_default,
132 reason = "preserve the imported PocketIC contract without an unrelated API change"
133 )]
134 pub fn new() -> Self {
135 let temp_dir = TempDir::new().unwrap();
136 Self {
137 state: PocketIcStateKind::TempDir(temp_dir),
138 }
139 }
140
141 pub fn new_from_path(state_dir: PathBuf) -> Self {
142 Self {
143 state: PocketIcStateKind::StateDir(state_dir),
144 }
145 }
146
147 pub fn into_path(self) -> PathBuf {
148 match self.state {
149 PocketIcStateKind::StateDir(state_dir) => state_dir,
150 PocketIcStateKind::TempDir(temp_dir) => temp_dir.keep(),
151 }
152 }
153
154 pub(crate) fn state_dir(&self) -> PathBuf {
155 match &self.state {
156 PocketIcStateKind::StateDir(state_dir) => state_dir.clone(),
157 PocketIcStateKind::TempDir(temp_dir) => temp_dir.path().to_path_buf(),
158 }
159 }
160}
161
162pub struct PocketIcBuilder {
163 config: Option<ExtendedSubnetConfigSet>,
164 http_gateway_config: Option<InstanceHttpGatewayConfig>,
165 server_url: Option<Url>,
166 max_request_time_ms: Option<u64>,
167 read_only_state_dir: Option<PathBuf>,
168 state_dir: Option<PocketIcState>,
169 icp_config: IcpConfig,
170 log_level: Option<Level>,
171 bitcoind_addr: Option<Vec<SocketAddr>>,
172 dogecoind_addr: Option<Vec<SocketAddr>>,
173 icp_features: IcpFeatures,
174 initial_time: Option<InitialTime>,
175 mainnet_nns_subnet_id: Option<bool>,
176 disable_ingress_validation: Option<bool>,
177}
178
179#[expect(
180 clippy::new_without_default,
181 reason = "preserve the imported PocketIC contract without an unrelated API change"
182)]
183impl PocketIcBuilder {
184 pub fn new() -> Self {
185 Self {
186 config: None,
187 http_gateway_config: None,
188 server_url: None,
189 max_request_time_ms: Some(DEFAULT_MAX_REQUEST_TIME_MS),
190 read_only_state_dir: None,
191 state_dir: None,
192 icp_config: IcpConfig::default(),
193 log_level: None,
194 bitcoind_addr: None,
195 dogecoind_addr: None,
196 icp_features: IcpFeatures::default(),
197 initial_time: None,
198 mainnet_nns_subnet_id: None,
199 disable_ingress_validation: None,
200 }
201 }
202
203 pub fn new_with_config(config: impl Into<ExtendedSubnetConfigSet>) -> Self {
204 let mut builder = Self::new();
205 builder.config = Some(config.into());
206 builder
207 }
208
209 pub fn build(self) -> Result<PocketIc, ClientError> {
210 PocketIc::from_components(
211 self.config.unwrap_or_default(),
212 self.server_url.ok_or(ClientError::MissingServerUrl)?,
213 self.max_request_time_ms,
214 self.read_only_state_dir,
215 self.state_dir,
216 self.icp_config,
217 self.log_level,
218 self.bitcoind_addr,
219 self.dogecoind_addr,
220 self.icp_features,
221 self.initial_time,
222 self.http_gateway_config,
223 self.mainnet_nns_subnet_id,
224 self.disable_ingress_validation,
225 )
226 }
227
228 pub async fn build_async(self) -> Result<PocketIcAsync, ClientError> {
229 PocketIcAsync::from_components(
230 self.config.unwrap_or_default(),
231 self.server_url.ok_or(ClientError::MissingServerUrl)?,
232 self.max_request_time_ms,
233 self.read_only_state_dir,
234 self.state_dir,
235 self.icp_config,
236 self.log_level,
237 self.bitcoind_addr,
238 self.dogecoind_addr,
239 self.icp_features,
240 self.initial_time,
241 self.http_gateway_config,
242 self.mainnet_nns_subnet_id,
243 self.disable_ingress_validation,
244 )
245 .await
246 }
247
248 pub fn with_server_url(mut self, server_url: Url) -> Self {
250 self.server_url = Some(server_url);
251 self
252 }
253
254 pub fn with_max_request_time_ms(mut self, max_request_time_ms: Option<u64>) -> Self {
255 self.max_request_time_ms = max_request_time_ms;
256 self
257 }
258
259 pub fn with_state_dir(mut self, state_dir: PathBuf) -> Self {
260 self.state_dir = Some(PocketIcState::new_from_path(state_dir));
261 self
262 }
263
264 pub fn with_state(mut self, state_dir: PocketIcState) -> Self {
265 self.state_dir = Some(state_dir);
266 self
267 }
268
269 pub fn with_read_only_state(mut self, read_only_state_dir: &PocketIcState) -> Self {
270 self.read_only_state_dir = Some(read_only_state_dir.state_dir());
271 self
272 }
273
274 pub fn with_icp_config(mut self, icp_config: IcpConfig) -> Self {
275 self.icp_config = icp_config;
276 self
277 }
278
279 pub fn with_log_level(mut self, log_level: Level) -> Self {
280 self.log_level = Some(log_level);
281 self
282 }
283
284 pub fn with_bitcoind_addr(self, bitcoind_addr: SocketAddr) -> Self {
285 self.with_bitcoind_addrs(vec![bitcoind_addr])
286 }
287
288 pub fn with_bitcoind_addrs(self, bitcoind_addrs: Vec<SocketAddr>) -> Self {
289 Self {
290 bitcoind_addr: Some(bitcoind_addrs),
291 ..self
292 }
293 }
294
295 pub fn with_dogecoind_addrs(self, dogecoind_addrs: Vec<SocketAddr>) -> Self {
296 Self {
297 dogecoind_addr: Some(dogecoind_addrs),
298 ..self
299 }
300 }
301
302 pub fn with_nns_subnet(mut self) -> Self {
304 let mut config = self.config.unwrap_or_default();
305 config.nns = Some(config.nns.unwrap_or_default());
306 self.config = Some(config);
307 self
308 }
309
310 pub fn with_nns_state(self, path_to_state: PathBuf) -> Self {
327 self.with_subnet_state(SubnetKind::NNS, path_to_state)
328 }
329
330 pub fn with_subnet_state(mut self, subnet_kind: SubnetKind, path_to_state: PathBuf) -> Self {
347 let mut config = self.config.unwrap_or_default();
348 #[cfg(not(windows))]
349 let state_dir = path_to_state;
350 #[cfg(windows)]
351 let state_dir = wsl_path(&path_to_state, "subnet state").into();
352 let subnet_spec = SubnetSpec::default().with_state_dir(state_dir);
353 match subnet_kind {
354 SubnetKind::NNS => config.nns = Some(subnet_spec),
355 SubnetKind::SNS => config.sns = Some(subnet_spec),
356 SubnetKind::II => config.ii = Some(subnet_spec),
357 SubnetKind::Fiduciary => config.fiduciary = Some(subnet_spec),
358 SubnetKind::Bitcoin => config.bitcoin = Some(subnet_spec),
359 SubnetKind::TestThresholdKeys => config.test_threshold_keys = Some(subnet_spec),
360 SubnetKind::Application => config.application.push(subnet_spec),
361 SubnetKind::CloudEngine => config.cloud_engine.push(subnet_spec),
362 SubnetKind::System => config.system.push(subnet_spec),
363 SubnetKind::VerifiedApplication => config.verified_application.push(subnet_spec),
364 };
365 self.config = Some(config);
366 self
367 }
368
369 pub fn with_sns_subnet(mut self) -> Self {
371 let mut config = self.config.unwrap_or_default();
372 config.sns = Some(config.sns.unwrap_or_default());
373 self.config = Some(config);
374 self
375 }
376
377 pub fn with_ii_subnet(mut self) -> Self {
379 let mut config = self.config.unwrap_or_default();
380 config.ii = Some(config.ii.unwrap_or_default());
381 self.config = Some(config);
382 self
383 }
384
385 pub fn with_fiduciary_subnet(mut self) -> Self {
387 let mut config = self.config.unwrap_or_default();
388 config.fiduciary = Some(config.fiduciary.unwrap_or_default());
389 self.config = Some(config);
390 self
391 }
392
393 pub fn with_bitcoin_subnet(mut self) -> Self {
395 let mut config = self.config.unwrap_or_default();
396 config.bitcoin = Some(config.bitcoin.unwrap_or_default());
397 self.config = Some(config);
398 self
399 }
400
401 pub fn with_test_threshold_keys_subnet(mut self) -> Self {
403 let mut config = self.config.unwrap_or_default();
404 config.test_threshold_keys = Some(config.test_threshold_keys.unwrap_or_default());
405 self.config = Some(config);
406 self
407 }
408
409 pub fn with_system_subnet(mut self) -> Self {
411 let mut config = self.config.unwrap_or_default();
412 config.system.push(SubnetSpec::default());
413 self.config = Some(config);
414 self
415 }
416
417 pub fn with_application_subnet(mut self) -> Self {
419 let mut config = self.config.unwrap_or_default();
420 config.application.push(SubnetSpec::default());
421 self.config = Some(config);
422 self
423 }
424
425 pub fn with_verified_application_subnet(mut self) -> Self {
427 let mut config = self.config.unwrap_or_default();
428 config.verified_application.push(SubnetSpec::default());
429 self.config = Some(config);
430 self
431 }
432
433 pub fn with_benchmarking_application_subnet(mut self) -> Self {
435 let mut config = self.config.unwrap_or_default();
436 config
437 .application
438 .push(SubnetSpec::default().with_benchmarking_instruction_config());
439 self.config = Some(config);
440 self
441 }
442
443 pub fn with_benchmarking_system_subnet(mut self) -> Self {
445 let mut config = self.config.unwrap_or_default();
446 config
447 .system
448 .push(SubnetSpec::default().with_benchmarking_instruction_config());
449 self.config = Some(config);
450 self
451 }
452
453 pub fn with_icp_features(mut self, icp_features: IcpFeatures) -> Self {
458 self.icp_features = icp_features;
459 self
460 }
461
462 #[deprecated(note = "Use `with_initial_time` instead")]
466 pub fn with_initial_timestamp(mut self, initial_timestamp_nanos: u64) -> Self {
467 self.initial_time = Some(InitialTime::Timestamp(RawTime {
468 nanos_since_epoch: initial_timestamp_nanos,
469 }));
470 self
471 }
472
473 pub fn with_initial_time(mut self, initial_time: Time) -> Self {
477 self.initial_time = Some(InitialTime::Timestamp(RawTime {
478 nanos_since_epoch: initial_time.as_nanos_since_unix_epoch(),
479 }));
480 self
481 }
482
483 pub fn with_auto_progress(mut self) -> Self {
489 let config = AutoProgressConfig {
490 artificial_delay_ms: None,
491 };
492 self.initial_time = Some(InitialTime::AutoProgress(config));
493 self
494 }
495
496 pub fn with_http_gateway(mut self, http_gateway_config: InstanceHttpGatewayConfig) -> Self {
497 self.http_gateway_config = Some(http_gateway_config);
498 self
499 }
500
501 pub fn with_mainnet_nns_subnet_id(mut self) -> Self {
502 self.mainnet_nns_subnet_id = Some(true);
503 self
504 }
505
506 pub fn disable_ingress_validation(mut self) -> Self {
507 self.disable_ingress_validation = Some(true);
508 self
509 }
510}
511
512#[derive(Copy, Clone, PartialEq, PartialOrd)]
515pub struct Time(Duration);
516
517impl Time {
518 pub fn as_nanos_since_unix_epoch(&self) -> u64 {
520 self.0.as_nanos().try_into().unwrap()
521 }
522
523 pub const fn from_nanos_since_unix_epoch(nanos: u64) -> Self {
524 Time(Duration::from_nanos(nanos))
525 }
526}
527
528impl std::fmt::Debug for Time {
529 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
530 let nanos_since_unix_epoch = self.as_nanos_since_unix_epoch();
531 write!(f, "{nanos_since_unix_epoch}")
532 }
533}
534
535impl std::ops::Add<Duration> for Time {
536 type Output = Time;
537 fn add(self, dur: Duration) -> Time {
538 Time(self.0 + dur)
539 }
540}
541
542impl From<SystemTime> for Time {
543 fn from(time: SystemTime) -> Self {
544 Self::from_nanos_since_unix_epoch(
545 time.duration_since(UNIX_EPOCH)
546 .unwrap()
547 .as_nanos()
548 .try_into()
549 .unwrap(),
550 )
551 }
552}
553
554impl TryFrom<Time> for SystemTime {
555 type Error = String;
556
557 fn try_from(time: Time) -> Result<SystemTime, String> {
558 let nanos = time.as_nanos_since_unix_epoch();
559 let system_time = UNIX_EPOCH + Duration::from_nanos(nanos);
560 let roundtrip: Time = system_time.into();
561 if roundtrip.as_nanos_since_unix_epoch() == nanos {
562 Ok(system_time)
563 } else {
564 Err(format!(
565 "Converting UNIX timestamp {nanos} in nanoseconds to SystemTime failed due to losing precision"
566 ))
567 }
568 }
569}
570
571#[derive(Clone, Debug)]
573pub enum CreateCanisterPlacement {
574 SubnetId(SubnetId),
576 CanisterId(CanisterId),
578}
579
580#[derive(Clone, Debug, Default)]
582pub struct CreateCanisterParams {
583 pub cycles: Option<u128>,
585 pub settings: Option<CanisterSettings>,
587 pub placement: Option<CreateCanisterPlacement>,
589}
590
591pub struct PocketIc {
593 pocket_ic: PocketIcAsync,
594 runtime: Runtime,
595 thread: Option<JoinHandle<()>>,
596}
597
598impl PocketIc {
599 pub fn new(server_url: Url) -> Result<Self, ClientError> {
602 PocketIcBuilder::new()
603 .with_server_url(server_url)
604 .with_application_subnet()
605 .build()
606 }
607
608 pub fn new_from_existing_instance(
613 server_url: Url,
614 instance_id: InstanceId,
615 max_request_time_ms: Option<u64>,
616 ) -> Self {
617 let (tx, rx) = channel();
618 let thread = thread::spawn(move || {
619 let rt = tokio::runtime::Builder::new_current_thread()
620 .enable_all()
621 .build()
622 .unwrap();
623 tx.send(rt).unwrap();
624 });
625 let runtime = rx.recv().unwrap();
626
627 let pocket_ic =
628 PocketIcAsync::new_from_existing_instance(server_url, instance_id, max_request_time_ms);
629
630 Self {
631 pocket_ic,
632 runtime,
633 thread: Some(thread),
634 }
635 }
636
637 #[expect(
638 clippy::too_many_arguments,
639 reason = "retained upstream client method signature"
640 )]
641 pub(crate) fn from_components(
642 subnet_config_set: impl Into<ExtendedSubnetConfigSet>,
643 server_url: Url,
644 max_request_time_ms: Option<u64>,
645 read_only_state_dir: Option<PathBuf>,
646 state_dir: Option<PocketIcState>,
647 icp_config: IcpConfig,
648 log_level: Option<Level>,
649 bitcoind_addr: Option<Vec<SocketAddr>>,
650 dogecoind_addr: Option<Vec<SocketAddr>>,
651 icp_features: IcpFeatures,
652 initial_time: Option<InitialTime>,
653 http_gateway_config: Option<InstanceHttpGatewayConfig>,
654 mainnet_nns_subnet_id: Option<bool>,
655 disable_ingress_validation: Option<bool>,
656 ) -> Result<Self, ClientError> {
657 let (tx, rx) = channel();
658 let thread = thread::spawn(move || {
659 let rt = tokio::runtime::Builder::new_current_thread()
660 .enable_all()
661 .build()
662 .unwrap();
663 tx.send(rt).unwrap();
664 });
665 let runtime = rx.recv().unwrap();
666
667 let pocket_ic = runtime.block_on(async {
668 PocketIcAsync::from_components(
669 subnet_config_set,
670 server_url,
671 max_request_time_ms,
672 read_only_state_dir,
673 state_dir,
674 icp_config,
675 log_level,
676 bitcoind_addr,
677 dogecoind_addr,
678 icp_features,
679 initial_time,
680 http_gateway_config,
681 mainnet_nns_subnet_id,
682 disable_ingress_validation,
683 )
684 .await
685 })?;
686
687 Ok(Self {
688 pocket_ic,
689 runtime,
690 thread: Some(thread),
691 })
692 }
693
694 pub fn drop_and_take_state(&mut self) -> Result<Option<PocketIcState>, ClientError> {
695 self.runtime.block_on(self.pocket_ic.do_drop())?;
696 Ok(self.pocket_ic.take_state_internal())
697 }
698
699 pub fn get_server_url(&self) -> Url {
701 self.pocket_ic.get_server_url()
702 }
703
704 pub fn instance_id(&self) -> InstanceId {
706 self.pocket_ic.instance_id
707 }
708
709 pub fn topology(&self) -> Result<Topology, ClientError> {
711 self.runtime.block_on(self.pocket_ic.topology())
712 }
713
714 #[instrument(ret, skip(self, blob), fields(instance_id=self.pocket_ic.instance_id, blob_len = %blob.len(), compression = ?compression))]
716 pub fn upload_blob(
717 &self,
718 blob: Vec<u8>,
719 compression: BlobCompression,
720 ) -> Result<BlobId, ClientError> {
721 self.runtime
722 .block_on(self.pocket_ic.upload_blob(blob, compression))
723 }
724
725 #[instrument(skip(self, data), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string(), data_len = %data.len(), compression = ?compression))]
728 pub fn set_stable_memory(
729 &self,
730 canister_id: CanisterId,
731 data: Vec<u8>,
732 compression: BlobCompression,
733 ) -> Result<(), ClientError> {
734 self.runtime.block_on(
735 self.pocket_ic
736 .set_stable_memory(canister_id, data, compression),
737 )
738 }
739
740 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string()))]
742 pub fn get_stable_memory(&self, canister_id: CanisterId) -> Result<Vec<u8>, ClientError> {
743 self.runtime
744 .block_on(self.pocket_ic.get_stable_memory(canister_id))
745 }
746
747 #[instrument(ret)]
749 pub fn list_instances(server_url: Url) -> Result<Vec<String>, ClientError> {
750 let runtime = tokio::runtime::Builder::new_current_thread()
751 .enable_all()
752 .build()?;
753 runtime.block_on(PocketIcAsync::list_instances(server_url))
754 }
755
756 #[instrument(skip_all, fields(instance_id=self.pocket_ic.instance_id))]
758 pub fn verify_canister_signature(
759 &self,
760 msg: Vec<u8>,
761 sig: Vec<u8>,
762 pubkey: Vec<u8>,
763 root_pubkey: Vec<u8>,
764 ) -> Result<(), ClientError> {
765 self.runtime
766 .block_on(
767 self.pocket_ic
768 .verify_canister_signature(msg, sig, pubkey, root_pubkey),
769 )
770 }
771
772 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id))]
777 pub fn tick(&self) -> Result<(), ClientError> {
778 self.runtime.block_on(self.pocket_ic.tick())
779 }
780
781 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id))]
784 pub fn tick_with_configs(&self, configs: TickConfigs) -> Result<(), ClientError> {
785 self.runtime
786 .block_on(self.pocket_ic.tick_with_configs(configs))
787 }
788
789 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id))]
797 pub fn auto_progress(&self) -> Result<Url, ClientError> {
798 self.runtime.block_on(self.pocket_ic.auto_progress())
799 }
800
801 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id))]
803 pub fn auto_progress_enabled(&self) -> Result<bool, ClientError> {
804 self.runtime
805 .block_on(self.pocket_ic.auto_progress_enabled())
806 }
807
808 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id))]
810 pub fn stop_progress(&self) -> Result<(), ClientError> {
811 self.runtime.block_on(self.pocket_ic.stop_progress())
812 }
813
814 pub fn url(&self) -> Option<Url> {
818 self.pocket_ic.url()
819 }
820
821 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id))]
832 pub fn make_live(&mut self, listen_at: Option<u16>) -> Result<Url, ClientError> {
833 self.runtime.block_on(self.pocket_ic.make_live(listen_at))
834 }
835
836 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id))]
849 pub fn make_live_with_params(
850 &mut self,
851 ip_addr: Option<IpAddr>,
852 listen_at: Option<u16>,
853 domains: Option<Vec<String>>,
854 https_config: Option<HttpsConfig>,
855 ) -> Result<Url, ClientError> {
856 self.runtime.block_on(self.pocket_ic.make_live_with_params(
857 ip_addr,
858 listen_at,
859 domains,
860 https_config,
861 ))
862 }
863
864 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id))]
867 pub fn stop_live(&mut self) -> Result<(), ClientError> {
868 self.runtime.block_on(self.pocket_ic.stop_live())
869 }
870
871 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id))]
873 pub fn root_key(&self) -> Result<Option<Vec<u8>>, ClientError> {
874 self.runtime.block_on(self.pocket_ic.root_key())
875 }
876
877 #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id))]
879 pub fn get_time(&self) -> Result<Time, ClientError> {
880 self.runtime.block_on(self.pocket_ic.get_time())
881 }
882
883 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, time = ?time))]
885 pub fn set_time(&self, time: Time) -> Result<(), ClientError> {
886 self.runtime.block_on(self.pocket_ic.set_time(time))
887 }
888
889 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, time = ?time))]
891 pub fn set_certified_time(&self, time: Time) -> Result<(), ClientError> {
892 self.runtime
893 .block_on(self.pocket_ic.set_certified_time(time))
894 }
895
896 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, duration = ?duration))]
898 pub fn advance_time(&self, duration: Duration) -> Result<(), ClientError> {
899 self.runtime.block_on(self.pocket_ic.advance_time(duration))
900 }
901
902 #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string()))]
905 pub fn get_controllers(&self, canister_id: CanisterId) -> Result<Vec<Principal>, ClientError> {
906 self.runtime
907 .block_on(self.pocket_ic.get_controllers(canister_id))
908 }
909
910 #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string()))]
912 pub fn cycle_balance(&self, canister_id: CanisterId) -> Result<u128, ClientError> {
913 self.runtime
914 .block_on(self.pocket_ic.cycle_balance(canister_id))
915 }
916
917 #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string(), amount = %amount))]
919 pub fn add_cycles(&self, canister_id: CanisterId, amount: u128) -> Result<u128, ClientError> {
920 self.runtime
921 .block_on(self.pocket_ic.add_cycles(canister_id, amount))
922 }
923
924 pub fn submit_call(
926 &self,
927 canister_id: CanisterId,
928 sender: Principal,
929 method: &str,
930 payload: Vec<u8>,
931 ) -> Result<RawMessageId, ClientError> {
932 self.runtime.block_on(
933 self.pocket_ic
934 .submit_call(canister_id, sender, method, payload),
935 )
936 }
937
938 pub fn submit_call_with_effective_principal(
940 &self,
941 canister_id: CanisterId,
942 effective_principal: RawEffectivePrincipal,
943 sender: Principal,
944 method: &str,
945 payload: Vec<u8>,
946 ) -> Result<RawMessageId, ClientError> {
947 self.runtime
948 .block_on(self.pocket_ic.submit_call_with_effective_principal(
949 canister_id,
950 effective_principal,
951 sender,
952 method,
953 payload,
954 ))
955 }
956
957 pub fn submit_call_with_effective_principal_and_sender_info(
959 &self,
960 canister_id: CanisterId,
961 effective_principal: RawEffectivePrincipal,
962 sender: Principal,
963 method: &str,
964 payload: Vec<u8>,
965 sender_info: RawSenderInfo,
966 ) -> Result<RawMessageId, ClientError> {
967 self.runtime.block_on(
968 self.pocket_ic
969 .submit_call_with_effective_principal_and_sender_info(
970 canister_id,
971 effective_principal,
972 sender,
973 method,
974 payload,
975 sender_info,
976 ),
977 )
978 }
979
980 pub fn submit_call_with_sender_info(
982 &self,
983 canister_id: CanisterId,
984 sender: Principal,
985 method: &str,
986 payload: Vec<u8>,
987 sender_info: RawSenderInfo,
988 ) -> Result<RawMessageId, ClientError> {
989 self.runtime
990 .block_on(self.pocket_ic.submit_call_with_sender_info(
991 canister_id,
992 sender,
993 method,
994 payload,
995 sender_info,
996 ))
997 }
998
999 pub fn await_call(&self, message_id: RawMessageId) -> Result<Vec<u8>, ClientError> {
1001 self.runtime.block_on(self.pocket_ic.await_call(message_id))
1002 }
1003
1004 pub fn ingress_status(
1008 &self,
1009 message_id: RawMessageId,
1010 ) -> Result<Option<Result<Vec<u8>, RejectResponse>>, ClientError> {
1011 self.runtime
1012 .block_on(self.pocket_ic.ingress_status(message_id))
1013 }
1014
1015 pub fn ingress_status_as(
1020 &self,
1021 message_id: RawMessageId,
1022 caller: Principal,
1023 ) -> Result<IngressStatusResult, ClientError> {
1024 self.runtime
1025 .block_on(self.pocket_ic.ingress_status_as(message_id, caller))
1026 }
1027
1028 pub fn await_call_no_ticks(&self, message_id: RawMessageId) -> Result<Vec<u8>, ClientError> {
1032 self.runtime
1033 .block_on(self.pocket_ic.await_call_no_ticks(message_id))
1034 }
1035
1036 #[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()))]
1038 pub fn update_call(
1039 &self,
1040 canister_id: CanisterId,
1041 sender: Principal,
1042 method: &str,
1043 payload: Vec<u8>,
1044 ) -> Result<Vec<u8>, ClientError> {
1045 self.runtime.block_on(
1046 self.pocket_ic
1047 .update_call(canister_id, sender, method, payload),
1048 )
1049 }
1050
1051 #[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()))]
1053 pub fn query_call(
1054 &self,
1055 canister_id: CanisterId,
1056 sender: Principal,
1057 method: &str,
1058 payload: Vec<u8>,
1059 ) -> Result<Vec<u8>, ClientError> {
1060 self.runtime.block_on(
1061 self.pocket_ic
1062 .query_call(canister_id, sender, method, payload),
1063 )
1064 }
1065
1066 pub fn fetch_canister_logs(
1068 &self,
1069 canister_id: CanisterId,
1070 sender: Principal,
1071 ) -> Result<Vec<CanisterLogRecord>, ClientError> {
1072 self.runtime
1073 .block_on(self.pocket_ic.fetch_canister_logs(canister_id, sender))
1074 }
1075
1076 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1078 pub fn canister_status(
1079 &self,
1080 canister_id: CanisterId,
1081 sender: Option<Principal>,
1082 ) -> Result<CanisterStatusResult, ClientError> {
1083 self.runtime
1084 .block_on(self.pocket_ic.canister_status(canister_id, sender))
1085 }
1086
1087 #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id))]
1090 pub fn create_canister(&self) -> Result<CanisterId, ClientError> {
1091 self.runtime.block_on(self.pocket_ic.create_canister())
1092 }
1093
1094 #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id, settings = ?settings, sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1097 pub fn create_canister_with_settings(
1098 &self,
1099 sender: Option<Principal>,
1100 settings: Option<CanisterSettings>,
1101 ) -> Result<CanisterId, ClientError> {
1102 self.runtime.block_on(
1103 self.pocket_ic
1104 .create_canister_with_settings(sender, settings),
1105 )
1106 }
1107
1108 #[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()))]
1117 pub fn create_canister_with_id(
1118 &self,
1119 sender: Option<Principal>,
1120 settings: Option<CanisterSettings>,
1121 canister_id: CanisterId,
1122 ) -> Result<CanisterId, ClientError> {
1123 self.runtime.block_on(
1124 self.pocket_ic
1125 .create_canister_with_id(sender, settings, canister_id),
1126 )
1127 }
1128
1129 #[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()))]
1132 pub fn create_canister_on_subnet(
1133 &self,
1134 sender: Option<Principal>,
1135 settings: Option<CanisterSettings>,
1136 subnet_id: SubnetId,
1137 ) -> Result<CanisterId, ClientError> {
1138 self.runtime.block_on(
1139 self.pocket_ic
1140 .create_canister_on_subnet(sender, settings, subnet_id),
1141 )
1142 }
1143
1144 #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id, sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1149 pub fn create_canister_with_params(
1150 &self,
1151 sender: Option<Principal>,
1152 params: CreateCanisterParams,
1153 ) -> Result<CanisterId, ClientError> {
1154 self.runtime
1155 .block_on(self.pocket_ic.create_canister_with_params(sender, params))
1156 }
1157
1158 #[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()))]
1161 pub fn upload_chunk(
1162 &self,
1163 canister_id: CanisterId,
1164 sender: Option<Principal>,
1165 chunk: Vec<u8>,
1166 ) -> Result<Vec<u8>, ClientError> {
1167 self.runtime
1168 .block_on(self.pocket_ic.upload_chunk(canister_id, sender, chunk))
1169 }
1170
1171 #[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()))]
1173 pub fn stored_chunks(
1174 &self,
1175 canister_id: CanisterId,
1176 sender: Option<Principal>,
1177 ) -> Result<Vec<Vec<u8>>, ClientError> {
1178 self.runtime
1179 .block_on(self.pocket_ic.stored_chunks(canister_id, sender))
1180 }
1181
1182 #[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()))]
1184 pub fn clear_chunk_store(
1185 &self,
1186 canister_id: CanisterId,
1187 sender: Option<Principal>,
1188 ) -> Result<(), ClientError> {
1189 self.runtime
1190 .block_on(self.pocket_ic.clear_chunk_store(canister_id, sender))
1191 }
1192
1193 #[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()))]
1195 pub fn install_chunked_canister(
1196 &self,
1197 canister_id: CanisterId,
1198 sender: Option<Principal>,
1199 mode: CanisterInstallMode,
1200 store_canister_id: CanisterId,
1201 chunk_hashes_list: Vec<Vec<u8>>,
1202 wasm_module_hash: Vec<u8>,
1203 arg: Vec<u8>,
1204 ) -> Result<(), ClientError> {
1205 self.runtime
1206 .block_on(self.pocket_ic.install_chunked_canister(
1207 canister_id,
1208 sender,
1209 mode,
1210 store_canister_id,
1211 chunk_hashes_list,
1212 wasm_module_hash,
1213 arg,
1214 ))
1215 }
1216
1217 #[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()))]
1219 pub fn install_canister(
1220 &self,
1221 canister_id: CanisterId,
1222 wasm_module: Vec<u8>,
1223 arg: Vec<u8>,
1224 sender: Option<Principal>,
1225 ) -> Result<(), ClientError> {
1226 self.runtime.block_on(self.pocket_ic.install_canister(
1227 canister_id,
1228 wasm_module,
1229 arg,
1230 sender,
1231 ))
1232 }
1233
1234 #[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()))]
1236 pub fn upgrade_canister(
1237 &self,
1238 canister_id: CanisterId,
1239 wasm_module: Vec<u8>,
1240 arg: Vec<u8>,
1241 sender: Option<Principal>,
1242 ) -> Result<(), ClientError> {
1243 self.runtime.block_on(self.pocket_ic.upgrade_canister(
1244 canister_id,
1245 wasm_module,
1246 arg,
1247 sender,
1248 ))
1249 }
1250
1251 #[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()))]
1253 pub fn upgrade_eop_canister(
1254 &self,
1255 canister_id: CanisterId,
1256 wasm_module: Vec<u8>,
1257 arg: Vec<u8>,
1258 sender: Option<Principal>,
1259 ) -> Result<(), ClientError> {
1260 self.runtime.block_on(self.pocket_ic.upgrade_eop_canister(
1261 canister_id,
1262 wasm_module,
1263 arg,
1264 sender,
1265 ))
1266 }
1267
1268 #[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()))]
1270 pub fn reinstall_canister(
1271 &self,
1272 canister_id: CanisterId,
1273 wasm_module: Vec<u8>,
1274 arg: Vec<u8>,
1275 sender: Option<Principal>,
1276 ) -> Result<(), ClientError> {
1277 self.runtime.block_on(self.pocket_ic.reinstall_canister(
1278 canister_id,
1279 wasm_module,
1280 arg,
1281 sender,
1282 ))
1283 }
1284
1285 #[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()))]
1287 pub fn uninstall_canister(
1288 &self,
1289 canister_id: CanisterId,
1290 sender: Option<Principal>,
1291 ) -> Result<(), ClientError> {
1292 self.runtime
1293 .block_on(self.pocket_ic.uninstall_canister(canister_id, sender))
1294 }
1295
1296 #[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()))]
1298 pub fn take_canister_snapshot(
1299 &self,
1300 canister_id: CanisterId,
1301 sender: Option<Principal>,
1302 replace_snapshot: Option<Vec<u8>>,
1303 ) -> Result<Snapshot, ClientError> {
1304 self.runtime.block_on(self.pocket_ic.take_canister_snapshot(
1305 canister_id,
1306 sender,
1307 replace_snapshot,
1308 ))
1309 }
1310
1311 #[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()))]
1313 pub fn load_canister_snapshot(
1314 &self,
1315 canister_id: CanisterId,
1316 sender: Option<Principal>,
1317 snapshot_id: Vec<u8>,
1318 ) -> Result<(), ClientError> {
1319 self.runtime.block_on(self.pocket_ic.load_canister_snapshot(
1320 canister_id,
1321 sender,
1322 snapshot_id,
1323 ))
1324 }
1325
1326 #[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()))]
1328 pub fn list_canister_snapshots(
1329 &self,
1330 canister_id: CanisterId,
1331 sender: Option<Principal>,
1332 ) -> Result<Vec<Snapshot>, ClientError> {
1333 self.runtime
1334 .block_on(self.pocket_ic.list_canister_snapshots(canister_id, sender))
1335 }
1336
1337 #[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()))]
1339 pub fn delete_canister_snapshot(
1340 &self,
1341 canister_id: CanisterId,
1342 sender: Option<Principal>,
1343 snapshot_id: Vec<u8>,
1344 ) -> Result<(), ClientError> {
1345 self.runtime
1346 .block_on(
1347 self.pocket_ic
1348 .delete_canister_snapshot(canister_id, sender, snapshot_id),
1349 )
1350 }
1351
1352 #[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()))]
1354 pub fn update_canister_settings(
1355 &self,
1356 canister_id: CanisterId,
1357 sender: Option<Principal>,
1358 settings: CanisterSettings,
1359 ) -> Result<(), ClientError> {
1360 self.runtime
1361 .block_on(
1362 self.pocket_ic
1363 .update_canister_settings(canister_id, sender, settings),
1364 )
1365 }
1366
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()))]
1369 pub fn set_controllers(
1370 &self,
1371 canister_id: CanisterId,
1372 sender: Option<Principal>,
1373 new_controllers: Vec<Principal>,
1374 ) -> Result<(), ClientError> {
1375 self.runtime.block_on(
1376 self.pocket_ic
1377 .set_controllers(canister_id, sender, new_controllers),
1378 )
1379 }
1380
1381 #[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()))]
1383 pub fn start_canister(
1384 &self,
1385 canister_id: CanisterId,
1386 sender: Option<Principal>,
1387 ) -> Result<(), ClientError> {
1388 self.runtime
1389 .block_on(self.pocket_ic.start_canister(canister_id, sender))
1390 }
1391
1392 #[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()))]
1394 pub fn stop_canister(
1395 &self,
1396 canister_id: CanisterId,
1397 sender: Option<Principal>,
1398 ) -> Result<(), ClientError> {
1399 self.runtime
1400 .block_on(self.pocket_ic.stop_canister(canister_id, sender))
1401 }
1402
1403 #[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()))]
1405 pub fn delete_canister(
1406 &self,
1407 canister_id: CanisterId,
1408 sender: Option<Principal>,
1409 ) -> Result<(), ClientError> {
1410 self.runtime
1411 .block_on(self.pocket_ic.delete_canister(canister_id, sender))
1412 }
1413
1414 #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string()))]
1416 pub fn canister_exists(&self, canister_id: CanisterId) -> Result<bool, ClientError> {
1417 self.runtime
1418 .block_on(self.pocket_ic.canister_exists(canister_id))
1419 }
1420
1421 #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id, subnet_id = %subnet_id.to_string()))]
1423 pub fn delete_subnet(&self, subnet_id: SubnetId) -> Result<(), ClientError> {
1424 self.runtime
1425 .block_on(self.pocket_ic.delete_subnet(subnet_id))
1426 }
1427
1428 #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string()))]
1430 pub fn get_subnet(&self, canister_id: CanisterId) -> Result<Option<SubnetId>, ClientError> {
1431 self.runtime
1432 .block_on(self.pocket_ic.get_subnet(canister_id))
1433 }
1434
1435 #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id, subnet_id = %subnet_id.to_string()))]
1437 pub fn get_subnet_metrics(
1438 &self,
1439 subnet_id: Principal,
1440 ) -> Result<Option<SubnetMetrics>, ClientError> {
1441 self.runtime
1442 .block_on(self.pocket_ic.get_subnet_metrics(subnet_id))
1443 }
1444
1445 pub fn update_call_with_effective_principal(
1446 &self,
1447 canister_id: CanisterId,
1448 effective_principal: RawEffectivePrincipal,
1449 sender: Principal,
1450 method: &str,
1451 payload: Vec<u8>,
1452 ) -> Result<Vec<u8>, ClientError> {
1453 self.runtime
1454 .block_on(self.pocket_ic.update_call_with_effective_principal(
1455 canister_id,
1456 effective_principal,
1457 sender,
1458 method,
1459 payload,
1460 ))
1461 }
1462
1463 pub fn update_call_with_effective_principal_and_sender_info(
1465 &self,
1466 canister_id: CanisterId,
1467 effective_principal: RawEffectivePrincipal,
1468 sender: Principal,
1469 method: &str,
1470 payload: Vec<u8>,
1471 sender_info: RawSenderInfo,
1472 ) -> Result<Vec<u8>, ClientError> {
1473 self.runtime.block_on(
1474 self.pocket_ic
1475 .update_call_with_effective_principal_and_sender_info(
1476 canister_id,
1477 effective_principal,
1478 sender,
1479 method,
1480 payload,
1481 sender_info,
1482 ),
1483 )
1484 }
1485
1486 pub fn update_call_with_sender_info(
1488 &self,
1489 canister_id: CanisterId,
1490 sender: Principal,
1491 method: &str,
1492 payload: Vec<u8>,
1493 sender_info: RawSenderInfo,
1494 ) -> Result<Vec<u8>, ClientError> {
1495 self.runtime
1496 .block_on(self.pocket_ic.update_call_with_sender_info(
1497 canister_id,
1498 sender,
1499 method,
1500 payload,
1501 sender_info,
1502 ))
1503 }
1504
1505 #[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()))]
1509 pub fn query_call_with_effective_principal(
1510 &self,
1511 canister_id: CanisterId,
1512 effective_principal: RawEffectivePrincipal,
1513 sender: Principal,
1514 method: &str,
1515 payload: Vec<u8>,
1516 ) -> Result<Vec<u8>, ClientError> {
1517 self.runtime
1518 .block_on(self.pocket_ic.query_call_with_effective_principal(
1519 canister_id,
1520 effective_principal,
1521 sender,
1522 method,
1523 payload,
1524 ))
1525 }
1526
1527 pub fn query_call_with_effective_principal_and_sender_info(
1529 &self,
1530 canister_id: CanisterId,
1531 effective_principal: RawEffectivePrincipal,
1532 sender: Principal,
1533 method: &str,
1534 payload: Vec<u8>,
1535 sender_info: RawSenderInfo,
1536 ) -> Result<Vec<u8>, ClientError> {
1537 self.runtime.block_on(
1538 self.pocket_ic
1539 .query_call_with_effective_principal_and_sender_info(
1540 canister_id,
1541 effective_principal,
1542 sender,
1543 method,
1544 payload,
1545 sender_info,
1546 ),
1547 )
1548 }
1549
1550 pub fn query_call_with_sender_info(
1552 &self,
1553 canister_id: CanisterId,
1554 sender: Principal,
1555 method: &str,
1556 payload: Vec<u8>,
1557 sender_info: RawSenderInfo,
1558 ) -> Result<Vec<u8>, ClientError> {
1559 self.runtime
1560 .block_on(self.pocket_ic.query_call_with_sender_info(
1561 canister_id,
1562 sender,
1563 method,
1564 payload,
1565 sender_info,
1566 ))
1567 }
1568
1569 #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id))]
1583 pub fn get_canister_http(&self) -> Result<Vec<CanisterHttpRequest>, ClientError> {
1584 self.runtime.block_on(self.pocket_ic.get_canister_http())
1585 }
1586
1587 #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id))]
1593 pub fn mock_canister_http_response(
1594 &self,
1595 mock_canister_http_response: MockCanisterHttpResponse,
1596 ) -> Result<(), ClientError> {
1597 self.runtime.block_on(
1598 self.pocket_ic
1599 .mock_canister_http_response(mock_canister_http_response),
1600 )
1601 }
1602
1603 #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id))]
1619 pub fn mock_flexible_canister_http_response(
1620 &self,
1621 mock_flexible_canister_http_response: MockFlexibleCanisterHttpResponse,
1622 ) -> Result<(), ClientError> {
1623 self.runtime.block_on(
1624 self.pocket_ic
1625 .mock_flexible_canister_http_response(mock_flexible_canister_http_response),
1626 )
1627 }
1628
1629 #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id))]
1633 pub fn canister_snapshot_download(
1634 &self,
1635 canister_id: CanisterId,
1636 sender: Principal,
1637 snapshot_id: Vec<u8>,
1638 snapshot_dir: PathBuf,
1639 ) -> Result<(), ClientError> {
1640 self.runtime
1641 .block_on(self.pocket_ic.canister_snapshot_download(
1642 canister_id,
1643 sender,
1644 snapshot_id,
1645 snapshot_dir,
1646 ))
1647 }
1648
1649 #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id))]
1653 pub fn canister_snapshot_upload(
1654 &self,
1655 canister_id: CanisterId,
1656 sender: Principal,
1657 replace_snapshot: Option<Vec<u8>>,
1658 snapshot_dir: PathBuf,
1659 ) -> Result<Vec<u8>, ClientError> {
1660 self.runtime
1661 .block_on(self.pocket_ic.canister_snapshot_upload(
1662 canister_id,
1663 sender,
1664 replace_snapshot,
1665 snapshot_dir,
1666 ))
1667 }
1668}
1669
1670impl Drop for PocketIc {
1671 fn drop(&mut self) {
1672 if !self.pocket_ic.cleanup_attempted {
1673 self.runtime.block_on(async {
1674 if let Err(error) = self.pocket_ic.do_drop().await {
1675 tracing::warn!("PocketIC drop failed: {error}");
1676 }
1677 });
1678 }
1679 if let Some(thread) = self.thread.take() {
1680 let _ = thread.join();
1681 }
1682 }
1683}
1684
1685pub fn call_candid_as<Input, Output>(
1689 env: &PocketIc,
1690 canister_id: CanisterId,
1691 effective_principal: RawEffectivePrincipal,
1692 sender: Principal,
1693 method: &str,
1694 input: Input,
1695) -> Result<Output, ClientError>
1696where
1697 Input: ArgumentEncoder,
1698 Output: for<'a> ArgumentDecoder<'a>,
1699{
1700 with_candid(input, |payload| {
1701 env.update_call_with_effective_principal(
1702 canister_id,
1703 effective_principal,
1704 sender,
1705 method,
1706 payload,
1707 )
1708 })
1709}
1710
1711pub fn call_candid<Input, Output>(
1714 env: &PocketIc,
1715 canister_id: CanisterId,
1716 effective_principal: RawEffectivePrincipal,
1717 method: &str,
1718 input: Input,
1719) -> Result<Output, ClientError>
1720where
1721 Input: ArgumentEncoder,
1722 Output: for<'a> ArgumentDecoder<'a>,
1723{
1724 call_candid_as(
1725 env,
1726 canister_id,
1727 effective_principal,
1728 Principal::anonymous(),
1729 method,
1730 input,
1731 )
1732}
1733
1734pub fn query_candid<Input, Output>(
1736 env: &PocketIc,
1737 canister_id: CanisterId,
1738 method: &str,
1739 input: Input,
1740) -> Result<Output, ClientError>
1741where
1742 Input: ArgumentEncoder,
1743 Output: for<'a> ArgumentDecoder<'a>,
1744{
1745 query_candid_as(env, canister_id, Principal::anonymous(), method, input)
1746}
1747
1748pub fn query_candid_as<Input, Output>(
1751 env: &PocketIc,
1752 canister_id: CanisterId,
1753 sender: Principal,
1754 method: &str,
1755 input: Input,
1756) -> Result<Output, ClientError>
1757where
1758 Input: ArgumentEncoder,
1759 Output: for<'a> ArgumentDecoder<'a>,
1760{
1761 with_candid(input, |bytes| {
1762 env.query_call(canister_id, sender, method, bytes)
1763 })
1764}
1765
1766pub fn update_candid<Input, Output>(
1768 env: &PocketIc,
1769 canister_id: CanisterId,
1770 method: &str,
1771 input: Input,
1772) -> Result<Output, ClientError>
1773where
1774 Input: ArgumentEncoder,
1775 Output: for<'a> ArgumentDecoder<'a>,
1776{
1777 update_candid_as(env, canister_id, Principal::anonymous(), method, input)
1778}
1779
1780pub fn update_candid_as<Input, Output>(
1783 env: &PocketIc,
1784 canister_id: CanisterId,
1785 sender: Principal,
1786 method: &str,
1787 input: Input,
1788) -> Result<Output, ClientError>
1789where
1790 Input: ArgumentEncoder,
1791 Output: for<'a> ArgumentDecoder<'a>,
1792{
1793 with_candid(input, |bytes| {
1794 env.update_call(canister_id, sender, method, bytes)
1795 })
1796}
1797
1798pub fn with_candid<Input, Output>(
1801 input: Input,
1802 f: impl FnOnce(Vec<u8>) -> Result<Vec<u8>, ClientError>,
1803) -> Result<Output, ClientError>
1804where
1805 Input: ArgumentEncoder,
1806 Output: for<'a> ArgumentDecoder<'a>,
1807{
1808 let bytes = encode_args(input)?;
1809 let output = f(bytes)?;
1810 Ok(decode_args(&output)?)
1811}
1812
1813#[derive(Clone, Copy, Debug)]
1815pub enum TryFromError {
1816 ValueOutOfRange(u64),
1817}
1818
1819#[derive(
1826 PartialOrd,
1827 Ord,
1828 Clone,
1829 Copy,
1830 Debug,
1831 PartialEq,
1832 Eq,
1833 Hash,
1834 Serialize,
1835 Deserialize,
1836 JsonSchema,
1837 EnumIter,
1838)]
1839pub enum ErrorCode {
1840 SubnetOversubscribed = 101,
1842 MaxNumberOfCanistersReached = 102,
1843 CanisterQueueFull = 201,
1845 IngressMessageTimeout = 202,
1846 CanisterQueueNotEmpty = 203,
1847 IngressHistoryFull = 204,
1848 CanisterIdAlreadyExists = 205,
1849 StopCanisterRequestTimeout = 206,
1850 CanisterOutOfCycles = 207,
1851 CertifiedStateUnavailable = 208,
1852 CanisterInstallCodeRateLimited = 209,
1853 CanisterHeapDeltaRateLimited = 210,
1854 SubnetCoolingDown = 211,
1855 CanisterNotFound = 301,
1857 CanisterSnapshotNotFound = 305,
1858 InsufficientMemoryAllocation = 402,
1860 InsufficientCyclesForCreateCanister = 403,
1861 SubnetNotFound = 404,
1862 CanisterNotHostedBySubnet = 405,
1863 CanisterRejectedMessage = 406,
1864 UnknownManagementMessage = 407,
1865 InvalidManagementPayload = 408,
1866 CanisterSnapshotImmutable = 409,
1867 InvalidSubnetAdmin = 410,
1868 CanisterTrapped = 502,
1870 CanisterCalledTrap = 503,
1871 CanisterContractViolation = 504,
1872 CanisterInvalidWasm = 505,
1873 CanisterDidNotReply = 506,
1874 CanisterOutOfMemory = 507,
1875 CanisterStopped = 508,
1876 CanisterStopping = 509,
1877 CanisterNotStopped = 510,
1878 CanisterStoppingCancelled = 511,
1879 CanisterInvalidController = 512,
1880 CanisterFunctionNotFound = 513,
1881 CanisterNonEmpty = 514,
1882 QueryCallGraphLoopDetected = 517,
1883 InsufficientCyclesInCall = 520,
1884 CanisterWasmEngineError = 521,
1885 CanisterInstructionLimitExceeded = 522,
1886 CanisterMemoryAccessLimitExceeded = 524,
1887 QueryCallGraphTooDeep = 525,
1888 QueryCallGraphTotalInstructionLimitExceeded = 526,
1889 CompositeQueryCalledInReplicatedMode = 527,
1890 QueryTimeLimitExceeded = 528,
1891 QueryCallGraphInternal = 529,
1892 InsufficientCyclesInComputeAllocation = 530,
1893 InsufficientCyclesInMemoryAllocation = 531,
1894 InsufficientCyclesInMemoryGrow = 532,
1895 ReservedCyclesLimitExceededInMemoryAllocation = 533,
1896 ReservedCyclesLimitExceededInMemoryGrow = 534,
1897 InsufficientCyclesInMessageMemoryGrow = 535,
1898 CanisterMethodNotFound = 536,
1899 CanisterWasmModuleNotFound = 537,
1900 CanisterAlreadyInstalled = 538,
1901 CanisterWasmMemoryLimitExceeded = 539,
1902 ReservedCyclesLimitIsTooLow = 540,
1903 CanisterInvalidControllerOrSubnetAdmin = 541,
1904 CanisterStatusAccessDenied = 542,
1905 DeadlineExpired = 601,
1907 ResponseDropped = 602,
1908}
1909
1910impl TryFrom<u64> for ErrorCode {
1911 type Error = TryFromError;
1912 fn try_from(err: u64) -> Result<ErrorCode, Self::Error> {
1913 match err {
1914 101 => Ok(ErrorCode::SubnetOversubscribed),
1916 102 => Ok(ErrorCode::MaxNumberOfCanistersReached),
1917 201 => Ok(ErrorCode::CanisterQueueFull),
1919 202 => Ok(ErrorCode::IngressMessageTimeout),
1920 203 => Ok(ErrorCode::CanisterQueueNotEmpty),
1921 204 => Ok(ErrorCode::IngressHistoryFull),
1922 205 => Ok(ErrorCode::CanisterIdAlreadyExists),
1923 206 => Ok(ErrorCode::StopCanisterRequestTimeout),
1924 207 => Ok(ErrorCode::CanisterOutOfCycles),
1925 208 => Ok(ErrorCode::CertifiedStateUnavailable),
1926 209 => Ok(ErrorCode::CanisterInstallCodeRateLimited),
1927 210 => Ok(ErrorCode::CanisterHeapDeltaRateLimited),
1928 211 => Ok(ErrorCode::SubnetCoolingDown),
1929 301 => Ok(ErrorCode::CanisterNotFound),
1931 305 => Ok(ErrorCode::CanisterSnapshotNotFound),
1932 402 => Ok(ErrorCode::InsufficientMemoryAllocation),
1934 403 => Ok(ErrorCode::InsufficientCyclesForCreateCanister),
1935 404 => Ok(ErrorCode::SubnetNotFound),
1936 405 => Ok(ErrorCode::CanisterNotHostedBySubnet),
1937 406 => Ok(ErrorCode::CanisterRejectedMessage),
1938 407 => Ok(ErrorCode::UnknownManagementMessage),
1939 408 => Ok(ErrorCode::InvalidManagementPayload),
1940 409 => Ok(ErrorCode::CanisterSnapshotImmutable),
1941 410 => Ok(ErrorCode::InvalidSubnetAdmin),
1942 502 => Ok(ErrorCode::CanisterTrapped),
1944 503 => Ok(ErrorCode::CanisterCalledTrap),
1945 504 => Ok(ErrorCode::CanisterContractViolation),
1946 505 => Ok(ErrorCode::CanisterInvalidWasm),
1947 506 => Ok(ErrorCode::CanisterDidNotReply),
1948 507 => Ok(ErrorCode::CanisterOutOfMemory),
1949 508 => Ok(ErrorCode::CanisterStopped),
1950 509 => Ok(ErrorCode::CanisterStopping),
1951 510 => Ok(ErrorCode::CanisterNotStopped),
1952 511 => Ok(ErrorCode::CanisterStoppingCancelled),
1953 512 => Ok(ErrorCode::CanisterInvalidController),
1954 513 => Ok(ErrorCode::CanisterFunctionNotFound),
1955 514 => Ok(ErrorCode::CanisterNonEmpty),
1956 517 => Ok(ErrorCode::QueryCallGraphLoopDetected),
1957 520 => Ok(ErrorCode::InsufficientCyclesInCall),
1958 521 => Ok(ErrorCode::CanisterWasmEngineError),
1959 522 => Ok(ErrorCode::CanisterInstructionLimitExceeded),
1960 524 => Ok(ErrorCode::CanisterMemoryAccessLimitExceeded),
1961 525 => Ok(ErrorCode::QueryCallGraphTooDeep),
1962 526 => Ok(ErrorCode::QueryCallGraphTotalInstructionLimitExceeded),
1963 527 => Ok(ErrorCode::CompositeQueryCalledInReplicatedMode),
1964 528 => Ok(ErrorCode::QueryTimeLimitExceeded),
1965 529 => Ok(ErrorCode::QueryCallGraphInternal),
1966 530 => Ok(ErrorCode::InsufficientCyclesInComputeAllocation),
1967 531 => Ok(ErrorCode::InsufficientCyclesInMemoryAllocation),
1968 532 => Ok(ErrorCode::InsufficientCyclesInMemoryGrow),
1969 533 => Ok(ErrorCode::ReservedCyclesLimitExceededInMemoryAllocation),
1970 534 => Ok(ErrorCode::ReservedCyclesLimitExceededInMemoryGrow),
1971 535 => Ok(ErrorCode::InsufficientCyclesInMessageMemoryGrow),
1972 536 => Ok(ErrorCode::CanisterMethodNotFound),
1973 537 => Ok(ErrorCode::CanisterWasmModuleNotFound),
1974 538 => Ok(ErrorCode::CanisterAlreadyInstalled),
1975 539 => Ok(ErrorCode::CanisterWasmMemoryLimitExceeded),
1976 540 => Ok(ErrorCode::ReservedCyclesLimitIsTooLow),
1977 541 => Ok(ErrorCode::CanisterInvalidControllerOrSubnetAdmin),
1978 542 => Ok(ErrorCode::CanisterStatusAccessDenied),
1979 601 => Ok(ErrorCode::DeadlineExpired),
1981 602 => Ok(ErrorCode::ResponseDropped),
1982 _ => Err(TryFromError::ValueOutOfRange(err)),
1983 }
1984 }
1985}
1986
1987impl std::fmt::Display for ErrorCode {
1988 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1989 write!(f, "IC{:04}", *self as i32)
1991 }
1992}
1993
1994#[derive(
1999 PartialOrd,
2000 Ord,
2001 Clone,
2002 Copy,
2003 Debug,
2004 PartialEq,
2005 Eq,
2006 Hash,
2007 Serialize,
2008 Deserialize,
2009 JsonSchema,
2010 EnumIter,
2011)]
2012pub enum RejectCode {
2013 SysFatal = 1,
2014 SysTransient = 2,
2015 DestinationInvalid = 3,
2016 CanisterReject = 4,
2017 CanisterError = 5,
2018 SysUnknown = 6,
2019}
2020
2021impl TryFrom<u64> for RejectCode {
2022 type Error = TryFromError;
2023 fn try_from(err: u64) -> Result<RejectCode, Self::Error> {
2024 match err {
2025 1 => Ok(RejectCode::SysFatal),
2026 2 => Ok(RejectCode::SysTransient),
2027 3 => Ok(RejectCode::DestinationInvalid),
2028 4 => Ok(RejectCode::CanisterReject),
2029 5 => Ok(RejectCode::CanisterError),
2030 6 => Ok(RejectCode::SysUnknown),
2031 _ => Err(TryFromError::ValueOutOfRange(err)),
2032 }
2033 }
2034}
2035
2036#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
2038pub struct RejectResponse {
2039 pub reject_code: RejectCode,
2040 pub reject_message: String,
2041 pub error_code: ErrorCode,
2042 pub certified: bool,
2043}
2044
2045impl std::fmt::Display for RejectResponse {
2046 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2047 write!(
2049 f,
2050 "PocketIC returned a rejection error: reject code {:?}, reject message {}, error code {:?}",
2051 self.reject_code, self.reject_message, self.error_code
2052 )
2053 }
2054}
2055
2056#[derive(Debug, Serialize, Deserialize)]
2062pub enum IngressStatusResult {
2063 NotAvailable,
2064 Forbidden(String),
2065 Success(Result<Vec<u8>, RejectResponse>),
2066}
2067
2068#[derive(Clone, Debug, Default)]
2069pub struct TickConfigs {
2070 pub blockmakers: Option<Vec<SubnetBlockmakers>>,
2071}
2072
2073impl From<TickConfigs> for RawTickConfigs {
2074 fn from(tick_configs: TickConfigs) -> Self {
2075 Self {
2076 blockmakers: tick_configs.blockmakers.map(|blockmakers| {
2077 blockmakers
2078 .into_iter()
2079 .map(|blockmaker| blockmaker.into())
2080 .collect()
2081 }),
2082 }
2083 }
2084}
2085
2086#[derive(Clone, Debug)]
2087pub struct SubnetBlockmakers {
2088 pub subnet: Principal,
2089 pub blockmaker: Principal,
2090 pub failed_blockmakers: Vec<Principal>,
2091}
2092
2093impl From<SubnetBlockmakers> for RawSubnetBlockmakers {
2094 fn from(blockmaker: SubnetBlockmakers) -> Self {
2095 Self {
2096 subnet: blockmaker.subnet.into(),
2097 blockmaker: blockmaker.blockmaker.into(),
2098 failed_blockmakers: blockmaker
2099 .failed_blockmakers
2100 .into_iter()
2101 .map(|p| p.into())
2102 .collect(),
2103 }
2104 }
2105}
2106
2107#[cfg_attr(
2115 all(not(windows), not(test)),
2116 expect(
2117 dead_code,
2118 reason = "the retained WSL converter is used by Windows implementations"
2119 )
2120)]
2121fn windows_to_wsl(path: &str) -> Result<String, String> {
2122 let path = path.strip_prefix(r"\\?\").unwrap_or(path);
2123 let mut chars = path.chars();
2124 let drive = match (chars.next(), chars.next()) {
2125 (Some(drive), Some(':')) if drive.is_ascii_alphabetic() => drive.to_ascii_lowercase(),
2126 _ => {
2127 return Err(format!(
2128 "`{path}` does not start with a drive prefix such as `C:`"
2129 ));
2130 }
2131 };
2132 let rest = chars.as_str();
2133 if !rest.is_empty() && !rest.starts_with(['\\', '/']) {
2134 return Err(format!("`{path}` is a drive-relative path"));
2135 }
2136 Ok(format!("/mnt/{drive}{}", rest.replace('\\', "/")))
2137}
2138
2139#[cfg(windows)]
2140fn wsl_path(path: &PathBuf, desc: &str) -> String {
2141 windows_to_wsl(
2142 path.as_os_str()
2143 .to_str()
2144 .unwrap_or_else(|| panic!("Could not convert {} path ({:?}) to String", desc, path)),
2145 )
2146 .unwrap_or_else(|e| {
2147 panic!(
2148 "Could not convert {} path ({:?}) to WSL path: {:?}",
2149 desc, path, e
2150 )
2151 })
2152}
2153
2154pub fn get_default_effective_canister_id(pocket_ic_url: String) -> Result<Principal, ClientError> {
2162 let runtime = Runtime::new()?;
2163 runtime
2164 .block_on(crate::pocket_ic::nonblocking::get_default_effective_canister_id(pocket_ic_url))
2165}
2166
2167pub fn copy_dir(
2168 src: impl AsRef<std::path::Path>,
2169 dst: impl AsRef<std::path::Path>,
2170) -> std::io::Result<()> {
2171 std::fs::create_dir_all(&dst)?;
2172 for entry in std::fs::read_dir(src)? {
2173 let entry = entry?;
2174 let ty = entry.file_type()?;
2175 if ty.is_dir() {
2176 copy_dir(entry.path(), dst.as_ref().join(entry.file_name()))?;
2177 } else {
2178 std::fs::copy(entry.path(), dst.as_ref().join(entry.file_name()))?;
2179 }
2180 }
2181 Ok(())
2182}
2183
2184#[cfg(test)]
2185mod tests;