pocket_ic/common/
rest.rs

1//! The PocketIC server and the PocketIc library interface with HTTP/JSON.
2//! The types in this module are used to serialize and deserialize data
3//! from and to JSON, and are used by both crates.
4
5use crate::RejectResponse;
6use candid::Principal;
7use hex;
8use reqwest::Response;
9use schemars::JsonSchema;
10use serde::de::DeserializeOwned;
11use serde::{Deserialize, Serialize};
12use std::collections::BTreeMap;
13use std::net::SocketAddr;
14use std::path::PathBuf;
15use strum_macros::EnumIter;
16
17pub type InstanceId = usize;
18
19#[derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize, JsonSchema)]
20pub struct AutoProgressConfig {
21    pub artificial_delay_ms: Option<u64>,
22}
23
24#[derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize, JsonSchema)]
25pub enum HttpGatewayBackend {
26    Replica(String),
27    PocketIcInstance(InstanceId),
28}
29
30#[derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize, JsonSchema)]
31pub struct HttpsConfig {
32    pub cert_path: String,
33    pub key_path: String,
34}
35
36#[derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize, JsonSchema)]
37pub struct InstanceHttpGatewayConfig {
38    pub ip_addr: Option<String>,
39    pub port: Option<u16>,
40    pub domains: Option<Vec<String>>,
41    pub https_config: Option<HttpsConfig>,
42}
43
44#[derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize, JsonSchema)]
45pub struct HttpGatewayConfig {
46    pub ip_addr: Option<String>,
47    pub port: Option<u16>,
48    pub forward_to: HttpGatewayBackend,
49    pub domains: Option<Vec<String>>,
50    pub https_config: Option<HttpsConfig>,
51}
52
53#[derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize, JsonSchema)]
54pub struct HttpGatewayDetails {
55    pub instance_id: InstanceId,
56    pub port: u16,
57    pub forward_to: HttpGatewayBackend,
58    pub domains: Option<Vec<String>>,
59    pub https_config: Option<HttpsConfig>,
60}
61
62#[derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize, JsonSchema)]
63pub struct HttpGatewayInfo {
64    pub instance_id: InstanceId,
65    pub port: u16,
66}
67
68#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
69pub enum CreateHttpGatewayResponse {
70    Created(HttpGatewayInfo),
71    Error { message: String },
72}
73
74#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
75pub enum CreateInstanceResponse {
76    Created {
77        instance_id: InstanceId,
78        topology: Topology,
79        http_gateway_info: Option<HttpGatewayInfo>,
80    },
81    Error {
82        message: String,
83    },
84}
85
86#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq, Serialize, Deserialize, JsonSchema)]
87pub struct RawTime {
88    pub nanos_since_epoch: u64,
89}
90
91/// Relevant for calls to the management canister. If a subnet ID is
92/// provided, the call will be sent to the management canister of that subnet.
93/// If a canister ID is provided, the call will be sent to the management
94/// canister of the subnet where the canister is on.
95/// If None, the call will be sent to any management canister.
96#[derive(Clone, Serialize, Deserialize, Debug, JsonSchema, PartialEq, Eq, Hash)]
97pub enum RawEffectivePrincipal {
98    None,
99    SubnetId(
100        #[serde(deserialize_with = "base64::deserialize")]
101        #[serde(serialize_with = "base64::serialize")]
102        Vec<u8>,
103    ),
104    CanisterId(
105        #[serde(deserialize_with = "base64::deserialize")]
106        #[serde(serialize_with = "base64::serialize")]
107        Vec<u8>,
108    ),
109}
110
111impl std::fmt::Display for RawEffectivePrincipal {
112    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
113        match self {
114            RawEffectivePrincipal::None => write!(f, "None"),
115            RawEffectivePrincipal::SubnetId(subnet_id) => {
116                let principal = Principal::from_slice(subnet_id);
117                write!(f, "SubnetId({})", principal)
118            }
119            RawEffectivePrincipal::CanisterId(canister_id) => {
120                let principal = Principal::from_slice(canister_id);
121                write!(f, "CanisterId({})", principal)
122            }
123        }
124    }
125}
126
127#[derive(Clone, Serialize, Deserialize, Debug, JsonSchema)]
128pub struct RawMessageId {
129    pub effective_principal: RawEffectivePrincipal,
130    #[serde(deserialize_with = "base64::deserialize")]
131    #[serde(serialize_with = "base64::serialize")]
132    pub message_id: Vec<u8>,
133}
134
135#[derive(Clone, Serialize, Deserialize, Debug, JsonSchema)]
136pub struct RawIngressStatusArgs {
137    pub raw_message_id: RawMessageId,
138    pub raw_caller: Option<RawPrincipalId>,
139}
140
141#[derive(Clone, Serialize, Deserialize, Debug, JsonSchema)]
142pub struct RawCanisterCall {
143    #[serde(deserialize_with = "base64::deserialize")]
144    #[serde(serialize_with = "base64::serialize")]
145    pub sender: Vec<u8>,
146    #[serde(deserialize_with = "base64::deserialize")]
147    #[serde(serialize_with = "base64::serialize")]
148    pub canister_id: Vec<u8>,
149    pub effective_principal: RawEffectivePrincipal,
150    pub method: String,
151    #[serde(deserialize_with = "base64::deserialize")]
152    #[serde(serialize_with = "base64::serialize")]
153    pub payload: Vec<u8>,
154}
155
156#[derive(Clone, Serialize, Deserialize, Debug, JsonSchema)]
157pub enum RawCanisterResult {
158    Ok(
159        #[serde(deserialize_with = "base64::deserialize")]
160        #[serde(serialize_with = "base64::serialize")]
161        Vec<u8>,
162    ),
163    Err(RejectResponse),
164}
165
166impl From<Result<Vec<u8>, RejectResponse>> for RawCanisterResult {
167    fn from(result: Result<Vec<u8>, RejectResponse>) -> Self {
168        match result {
169            Ok(data) => RawCanisterResult::Ok(data),
170            Err(reject_response) => RawCanisterResult::Err(reject_response),
171        }
172    }
173}
174
175impl From<RawCanisterResult> for Result<Vec<u8>, RejectResponse> {
176    fn from(result: RawCanisterResult) -> Self {
177        match result {
178            RawCanisterResult::Ok(data) => Ok(data),
179            RawCanisterResult::Err(reject_response) => Err(reject_response),
180        }
181    }
182}
183
184#[derive(Clone, Serialize, Deserialize, Debug, JsonSchema)]
185pub struct RawSetStableMemory {
186    #[serde(deserialize_with = "base64::deserialize")]
187    #[serde(serialize_with = "base64::serialize")]
188    pub canister_id: Vec<u8>,
189    pub blob_id: BlobId,
190}
191
192#[derive(Clone, Serialize, Deserialize, Debug, JsonSchema)]
193pub struct RawStableMemory {
194    #[serde(deserialize_with = "base64::deserialize")]
195    #[serde(serialize_with = "base64::serialize")]
196    pub blob: Vec<u8>,
197}
198
199#[derive(Clone, Serialize, Deserialize, Debug, JsonSchema)]
200pub struct ApiError {
201    message: String,
202}
203
204#[derive(Clone, Serialize, Deserialize, Debug, JsonSchema)]
205pub struct StartedOrBusyResponse {
206    pub state_label: String,
207    pub op_id: String,
208}
209
210#[derive(Clone, Serialize, Deserialize, Debug, JsonSchema)]
211#[serde(untagged)]
212pub enum ApiResponse<T> {
213    Success(T),
214    Busy { state_label: String, op_id: String },
215    Started { state_label: String, op_id: String },
216    Error { message: String },
217}
218
219impl<T: DeserializeOwned> ApiResponse<T> {
220    pub async fn from_response(resp: Response) -> Self {
221        match resp.status() {
222            reqwest::StatusCode::OK => {
223                let result = resp.json::<T>().await;
224                match result {
225                    Ok(t) => ApiResponse::Success(t),
226                    Err(e) => ApiResponse::Error {
227                        message: format!("Could not parse response: {}", e),
228                    },
229                }
230            }
231            reqwest::StatusCode::ACCEPTED => {
232                let result = resp.json::<StartedOrBusyResponse>().await;
233                match result {
234                    Ok(StartedOrBusyResponse { state_label, op_id }) => {
235                        ApiResponse::Started { state_label, op_id }
236                    }
237                    Err(e) => ApiResponse::Error {
238                        message: format!("Could not parse response: {}", e),
239                    },
240                }
241            }
242            reqwest::StatusCode::CONFLICT => {
243                let result = resp.json::<StartedOrBusyResponse>().await;
244                match result {
245                    Ok(StartedOrBusyResponse { state_label, op_id }) => {
246                        ApiResponse::Busy { state_label, op_id }
247                    }
248                    Err(e) => ApiResponse::Error {
249                        message: format!("Could not parse response: {}", e),
250                    },
251                }
252            }
253            _ => {
254                let result = resp.json::<ApiError>().await;
255                match result {
256                    Ok(e) => ApiResponse::Error { message: e.message },
257                    Err(e) => ApiResponse::Error {
258                        message: format!("Could not parse error: {}", e),
259                    },
260                }
261            }
262        }
263    }
264}
265
266#[derive(Clone, Serialize, Deserialize, Debug, JsonSchema)]
267pub struct RawAddCycles {
268    #[serde(deserialize_with = "base64::deserialize")]
269    #[serde(serialize_with = "base64::serialize")]
270    pub canister_id: Vec<u8>,
271    pub amount: u128,
272}
273
274#[derive(Clone, Serialize, Deserialize, Debug, JsonSchema)]
275pub struct RawCycles {
276    pub cycles: u128,
277}
278
279#[derive(Clone, Serialize, Eq, PartialEq, Ord, PartialOrd, Deserialize, Debug, JsonSchema)]
280pub struct RawPrincipalId {
281    // raw bytes of the principal
282    #[serde(deserialize_with = "base64::deserialize")]
283    #[serde(serialize_with = "base64::serialize")]
284    pub principal_id: Vec<u8>,
285}
286
287impl From<Principal> for RawPrincipalId {
288    fn from(principal: Principal) -> Self {
289        Self {
290            principal_id: principal.as_slice().to_vec(),
291        }
292    }
293}
294
295impl From<RawPrincipalId> for Principal {
296    fn from(raw_principal_id: RawPrincipalId) -> Self {
297        Principal::from_slice(&raw_principal_id.principal_id)
298    }
299}
300
301#[derive(Clone, Serialize, Eq, PartialEq, Ord, PartialOrd, Deserialize, Debug, JsonSchema)]
302pub struct RawCanisterId {
303    // raw bytes of the principal
304    #[serde(deserialize_with = "base64::deserialize")]
305    #[serde(serialize_with = "base64::serialize")]
306    pub canister_id: Vec<u8>,
307}
308
309impl From<Principal> for RawCanisterId {
310    fn from(principal: Principal) -> Self {
311        Self {
312            canister_id: principal.as_slice().to_vec(),
313        }
314    }
315}
316
317impl From<RawCanisterId> for Principal {
318    fn from(raw_canister_id: RawCanisterId) -> Self {
319        Principal::from_slice(&raw_canister_id.canister_id)
320    }
321}
322
323#[derive(Clone, Serialize, Deserialize, Debug, JsonSchema, PartialEq, Eq, Hash)]
324pub struct RawSubnetId {
325    #[serde(deserialize_with = "base64::deserialize")]
326    #[serde(serialize_with = "base64::serialize")]
327    pub subnet_id: Vec<u8>,
328}
329
330pub type SubnetId = Principal;
331
332impl From<Principal> for RawSubnetId {
333    fn from(principal: Principal) -> Self {
334        Self {
335            subnet_id: principal.as_slice().to_vec(),
336        }
337    }
338}
339
340impl From<RawSubnetId> for Principal {
341    fn from(val: RawSubnetId) -> Self {
342        Principal::from_slice(&val.subnet_id)
343    }
344}
345
346#[derive(
347    Clone, Serialize, Deserialize, Debug, JsonSchema, PartialEq, Eq, PartialOrd, Ord, Hash,
348)]
349pub struct RawNodeId {
350    #[serde(deserialize_with = "base64::deserialize")]
351    #[serde(serialize_with = "base64::serialize")]
352    pub node_id: Vec<u8>,
353}
354
355impl From<RawNodeId> for Principal {
356    fn from(val: RawNodeId) -> Self {
357        Principal::from_slice(&val.node_id)
358    }
359}
360
361impl From<Principal> for RawNodeId {
362    fn from(principal: Principal) -> Self {
363        Self {
364            node_id: principal.as_slice().to_vec(),
365        }
366    }
367}
368
369#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, JsonSchema, Default)]
370pub struct TickConfigs {
371    pub blockmakers: Option<BlockmakerConfigs>,
372}
373
374#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, JsonSchema)]
375pub struct BlockmakerConfigs {
376    pub blockmakers_per_subnet: Vec<RawSubnetBlockmaker>,
377}
378
379#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, JsonSchema)]
380pub struct RawSubnetBlockmaker {
381    pub subnet: RawSubnetId,
382    pub blockmaker: RawNodeId,
383    pub failed_blockmakers: Vec<RawNodeId>,
384}
385
386#[derive(Serialize, Deserialize, JsonSchema)]
387pub struct RawVerifyCanisterSigArg {
388    #[serde(deserialize_with = "base64::deserialize")]
389    #[serde(serialize_with = "base64::serialize")]
390    pub msg: Vec<u8>,
391    #[serde(deserialize_with = "base64::deserialize")]
392    #[serde(serialize_with = "base64::serialize")]
393    pub sig: Vec<u8>,
394    #[serde(deserialize_with = "base64::deserialize")]
395    #[serde(serialize_with = "base64::serialize")]
396    pub pubkey: Vec<u8>,
397    #[serde(deserialize_with = "base64::deserialize")]
398    #[serde(serialize_with = "base64::serialize")]
399    pub root_pubkey: Vec<u8>,
400}
401
402#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize, JsonSchema)]
403pub struct BlobId(
404    #[serde(deserialize_with = "base64::deserialize")]
405    #[serde(serialize_with = "base64::serialize")]
406    pub Vec<u8>,
407);
408
409impl std::fmt::Display for BlobId {
410    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
411        write!(f, "BlobId{{{}}}", hex::encode(self.0.clone()))
412    }
413}
414
415#[derive(Clone, Debug)]
416pub struct BinaryBlob {
417    pub data: Vec<u8>,
418    pub compression: BlobCompression,
419}
420
421#[derive(Clone, Copy, Debug, Eq, PartialEq, JsonSchema)]
422pub enum BlobCompression {
423    Gzip,
424    NoCompression,
425}
426
427// By default, serde serializes Vec<u8> to a list of numbers, which is inefficient.
428// This enables serializing Vec<u8> to a compact base64 representation.
429#[allow(deprecated)]
430pub mod base64 {
431    use serde::{Deserialize, Serialize};
432    use serde::{Deserializer, Serializer};
433
434    pub fn serialize<S: Serializer>(v: &Vec<u8>, s: S) -> Result<S::Ok, S::Error> {
435        let base64 = base64::encode(v);
436        String::serialize(&base64, s)
437    }
438
439    pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<Vec<u8>, D::Error> {
440        let base64 = String::deserialize(d)?;
441        base64::decode(base64.as_bytes()).map_err(serde::de::Error::custom)
442    }
443}
444
445// ================================================================================================================= //
446
447#[derive(
448    Debug,
449    Clone,
450    Copy,
451    Eq,
452    Hash,
453    PartialEq,
454    Ord,
455    PartialOrd,
456    Serialize,
457    Deserialize,
458    JsonSchema,
459    EnumIter,
460)]
461pub enum SubnetKind {
462    Application,
463    Bitcoin,
464    Fiduciary,
465    II,
466    NNS,
467    SNS,
468    System,
469    VerifiedApplication,
470}
471
472/// This represents which named subnets the user wants to create, and how
473/// many of the general app/system subnets, which are indistinguishable.
474#[derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize, Default, JsonSchema)]
475pub struct SubnetConfigSet {
476    pub nns: bool,
477    pub sns: bool,
478    pub ii: bool,
479    pub fiduciary: bool,
480    pub bitcoin: bool,
481    pub system: usize,
482    pub application: usize,
483    pub verified_application: usize,
484}
485
486impl SubnetConfigSet {
487    pub fn validate(&self) -> Result<(), String> {
488        if self.system > 0
489            || self.application > 0
490            || self.verified_application > 0
491            || self.nns
492            || self.sns
493            || self.ii
494            || self.fiduciary
495            || self.bitcoin
496        {
497            return Ok(());
498        }
499        Err("SubnetConfigSet must contain at least one subnet".to_owned())
500    }
501}
502
503impl From<SubnetConfigSet> for ExtendedSubnetConfigSet {
504    fn from(
505        SubnetConfigSet {
506            nns,
507            sns,
508            ii,
509            fiduciary: fid,
510            bitcoin,
511            system,
512            application,
513            verified_application,
514        }: SubnetConfigSet,
515    ) -> Self {
516        ExtendedSubnetConfigSet {
517            nns: if nns {
518                Some(SubnetSpec::default())
519            } else {
520                None
521            },
522            sns: if sns {
523                Some(SubnetSpec::default())
524            } else {
525                None
526            },
527            ii: if ii {
528                Some(SubnetSpec::default())
529            } else {
530                None
531            },
532            fiduciary: if fid {
533                Some(SubnetSpec::default())
534            } else {
535                None
536            },
537            bitcoin: if bitcoin {
538                Some(SubnetSpec::default())
539            } else {
540                None
541            },
542            system: vec![SubnetSpec::default(); system],
543            application: vec![SubnetSpec::default(); application],
544            verified_application: vec![SubnetSpec::default(); verified_application],
545        }
546    }
547}
548
549#[derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize, JsonSchema)]
550pub enum IcpConfigFlag {
551    Disabled,
552    Enabled,
553}
554
555/// Specifies ICP config of this instance.
556#[derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize, Default, JsonSchema)]
557pub struct IcpConfig {
558    /// Beta features (disabled on the ICP mainnet).
559    pub beta_features: Option<IcpConfigFlag>,
560    /// Canister backtraces (enabled on the ICP mainnet).
561    pub canister_backtrace: Option<IcpConfigFlag>,
562    /// Limits on function name length in canister WASM (enabled on the ICP mainnet).
563    pub function_name_length_limits: Option<IcpConfigFlag>,
564    /// Rate-limiting of canister execution (enabled on the ICP mainnet).
565    /// Canister execution refers to instructions and memory writes here.
566    pub canister_execution_rate_limiting: Option<IcpConfigFlag>,
567}
568
569#[derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize, Default, JsonSchema)]
570pub enum IcpFeaturesConfig {
571    #[default]
572    DefaultConfig,
573}
574
575/// Specifies ICP features enabled by deploying their corresponding system canisters
576/// when creating a PocketIC instance and keeping them up to date
577/// during the PocketIC instance lifetime.
578#[derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize, Default, JsonSchema)]
579pub struct IcpFeatures {
580    pub registry: Option<IcpFeaturesConfig>,
581    /// If the `cycles_minting` feature is enabled, then the default timestamp of a PocketIC instance is set to 10 May 2021 10:00:01 AM CEST (the smallest value that is strictly larger than the default timestamp hard-coded in the CMC state).
582    pub cycles_minting: Option<IcpFeaturesConfig>,
583    pub icp_token: Option<IcpFeaturesConfig>,
584    pub cycles_token: Option<IcpFeaturesConfig>,
585    pub nns_governance: Option<IcpFeaturesConfig>,
586    pub sns: Option<IcpFeaturesConfig>,
587    pub ii: Option<IcpFeaturesConfig>,
588    pub nns_ui: Option<IcpFeaturesConfig>,
589}
590
591#[derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize, JsonSchema)]
592pub enum InitialTime {
593    /// Sets the initial timestamp of the new instance to the provided value which must be at least
594    /// - 10 May 2021 10:00:01 AM CEST if the `cycles_minting` feature is enabled in `icp_features`;
595    /// - 06 May 2021 21:17:10 CEST otherwise.
596    Timestamp(RawTime),
597    /// Configures the new instance to make progress automatically,
598    /// i.e., periodically update the time of the IC instance
599    /// to the real time and execute rounds on the subnets.
600    AutoProgress(AutoProgressConfig),
601}
602
603#[derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize, Default, JsonSchema)]
604pub enum IncompleteStateFlag {
605    #[default]
606    Disabled,
607    Enabled,
608}
609
610#[derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize, Default, JsonSchema)]
611pub struct InstanceConfig {
612    pub subnet_config_set: ExtendedSubnetConfigSet,
613    pub http_gateway_config: Option<InstanceHttpGatewayConfig>,
614    pub state_dir: Option<PathBuf>,
615    pub icp_config: Option<IcpConfig>,
616    pub log_level: Option<String>,
617    pub bitcoind_addr: Option<Vec<SocketAddr>>,
618    pub icp_features: Option<IcpFeatures>,
619    pub incomplete_state: Option<IncompleteStateFlag>,
620    pub initial_time: Option<InitialTime>,
621}
622
623#[derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize, Default, JsonSchema)]
624pub struct ExtendedSubnetConfigSet {
625    pub nns: Option<SubnetSpec>,
626    pub sns: Option<SubnetSpec>,
627    pub ii: Option<SubnetSpec>,
628    pub fiduciary: Option<SubnetSpec>,
629    pub bitcoin: Option<SubnetSpec>,
630    pub system: Vec<SubnetSpec>,
631    pub application: Vec<SubnetSpec>,
632    pub verified_application: Vec<SubnetSpec>,
633}
634
635/// Specifies various configurations for a subnet.
636#[derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize, JsonSchema)]
637pub struct SubnetSpec {
638    state_config: SubnetStateConfig,
639    instruction_config: SubnetInstructionConfig,
640}
641
642impl SubnetSpec {
643    pub fn with_state_dir(mut self, path: PathBuf) -> SubnetSpec {
644        self.state_config = SubnetStateConfig::FromPath(path);
645        self
646    }
647
648    pub fn with_benchmarking_instruction_config(mut self) -> SubnetSpec {
649        self.instruction_config = SubnetInstructionConfig::Benchmarking;
650        self
651    }
652
653    pub fn get_state_path(&self) -> Option<PathBuf> {
654        self.state_config.get_path()
655    }
656
657    pub fn get_instruction_config(&self) -> SubnetInstructionConfig {
658        self.instruction_config.clone()
659    }
660
661    pub fn is_supported(&self) -> bool {
662        match &self.state_config {
663            SubnetStateConfig::New => true,
664            SubnetStateConfig::FromPath(..) => true,
665            SubnetStateConfig::FromBlobStore(..) => false,
666        }
667    }
668}
669
670impl Default for SubnetSpec {
671    fn default() -> Self {
672        Self {
673            state_config: SubnetStateConfig::New,
674            instruction_config: SubnetInstructionConfig::Production,
675        }
676    }
677}
678
679/// Specifies instruction limits for canister execution on this subnet.
680#[derive(
681    Debug, Clone, Eq, Hash, PartialEq, Ord, PartialOrd, Serialize, Deserialize, JsonSchema,
682)]
683pub enum SubnetInstructionConfig {
684    /// Use default instruction limits as in production.
685    Production,
686    /// Use very high instruction limits useful for asymptotic canister benchmarking.
687    Benchmarking,
688}
689
690/// Specifies whether the subnet should be created from scratch or loaded
691/// from a path.
692#[derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize, JsonSchema)]
693pub enum SubnetStateConfig {
694    /// Create new subnet with empty state.
695    New,
696    /// Load existing subnet state from the given path.
697    /// The path must be on a filesystem accessible to the server process.
698    FromPath(PathBuf),
699    /// Load existing subnet state from blobstore. Needs to be uploaded first!
700    /// Not implemented!
701    FromBlobStore(BlobId),
702}
703
704impl SubnetStateConfig {
705    pub fn get_path(&self) -> Option<PathBuf> {
706        match self {
707            SubnetStateConfig::FromPath(path) => Some(path.clone()),
708            SubnetStateConfig::FromBlobStore(_) => None,
709            SubnetStateConfig::New => None,
710        }
711    }
712}
713
714impl ExtendedSubnetConfigSet {
715    // Return the configured named subnets in order.
716    #[allow(clippy::type_complexity)]
717    pub fn get_named(&self) -> Vec<(SubnetKind, Option<PathBuf>, SubnetInstructionConfig)> {
718        use SubnetKind::*;
719        vec![
720            (self.nns.clone(), NNS),
721            (self.sns.clone(), SNS),
722            (self.ii.clone(), II),
723            (self.fiduciary.clone(), Fiduciary),
724            (self.bitcoin.clone(), Bitcoin),
725        ]
726        .into_iter()
727        .filter(|(mb, _)| mb.is_some())
728        .map(|(mb, kind)| {
729            let spec = mb.unwrap();
730            (kind, spec.get_state_path(), spec.get_instruction_config())
731        })
732        .collect()
733    }
734
735    pub fn validate(&self) -> Result<(), String> {
736        if !self.system.is_empty()
737            || !self.application.is_empty()
738            || !self.verified_application.is_empty()
739            || self.nns.is_some()
740            || self.sns.is_some()
741            || self.ii.is_some()
742            || self.fiduciary.is_some()
743            || self.bitcoin.is_some()
744        {
745            return Ok(());
746        }
747        Err("ExtendedSubnetConfigSet must contain at least one subnet".to_owned())
748    }
749
750    pub fn try_with_icp_features(mut self, icp_features: &IcpFeatures) -> Result<Self, String> {
751        let check_empty_subnet = |subnet: &Option<SubnetSpec>, subnet_desc, icp_feature| {
752            if let Some(config) = subnet {
753                if !matches!(config.state_config, SubnetStateConfig::New) {
754                    return Err(format!(
755                        "The {} subnet must be empty when specifying the `{}` ICP feature.",
756                        subnet_desc, icp_feature
757                    ));
758                }
759            }
760            Ok(())
761        };
762        // using `let IcpFeatures { }` with explicit field names
763        // to force an update after adding a new field to `IcpFeatures`
764        let IcpFeatures {
765            registry,
766            cycles_minting,
767            icp_token,
768            cycles_token,
769            nns_governance,
770            sns,
771            ii,
772            nns_ui,
773        } = icp_features;
774        // NNS canisters
775        for (flag, icp_feature_str) in [
776            (registry, "registry"),
777            (cycles_minting, "cycles_minting"),
778            (icp_token, "icp_token"),
779            (nns_governance, "nns_governance"),
780            (sns, "sns"),
781            (nns_ui, "nns_ui"),
782        ] {
783            if flag.is_some() {
784                check_empty_subnet(&self.nns, "NNS", icp_feature_str)?;
785                self.nns = Some(self.nns.unwrap_or_default());
786            }
787        }
788        // canisters on the II subnet
789        for (flag, icp_feature_str) in [(cycles_token, "cycles_token"), (ii, "ii")] {
790            if flag.is_some() {
791                check_empty_subnet(&self.ii, "II", icp_feature_str)?;
792                self.ii = Some(self.ii.unwrap_or_default());
793            }
794        }
795        // canisters on the SNS subnet
796        for (flag, icp_feature_str) in [(sns, "sns")] {
797            if flag.is_some() {
798                check_empty_subnet(&self.sns, "SNS", icp_feature_str)?;
799                self.sns = Some(self.sns.unwrap_or_default());
800            }
801        }
802        Ok(self)
803    }
804}
805
806/// Configuration details for a subnet, returned by PocketIc server
807#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize, JsonSchema)]
808pub struct SubnetConfig {
809    pub subnet_kind: SubnetKind,
810    pub subnet_seed: [u8; 32],
811    /// Instruction limits for canister execution on this subnet.
812    pub instruction_config: SubnetInstructionConfig,
813    /// Some mainnet subnets have several disjunct canister ranges.
814    pub canister_ranges: Vec<CanisterIdRange>,
815}
816
817#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize, JsonSchema)]
818pub struct CanisterIdRange {
819    pub start: RawCanisterId,
820    pub end: RawCanisterId,
821}
822
823impl CanisterIdRange {
824    fn contains(&self, canister_id: Principal) -> bool {
825        Principal::from_slice(&self.start.canister_id) <= canister_id
826            && canister_id <= Principal::from_slice(&self.end.canister_id)
827    }
828}
829
830#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize, JsonSchema)]
831pub struct Topology {
832    pub subnet_configs: BTreeMap<SubnetId, SubnetConfig>,
833    pub default_effective_canister_id: RawCanisterId,
834}
835
836impl Topology {
837    pub fn get_subnet(&self, canister_id: Principal) -> Option<SubnetId> {
838        self.subnet_configs
839            .iter()
840            .find(|(_, config)| {
841                config
842                    .canister_ranges
843                    .iter()
844                    .any(|r| r.contains(canister_id))
845            })
846            .map(|(subnet_id, _)| subnet_id)
847            .copied()
848    }
849
850    pub fn get_app_subnets(&self) -> Vec<SubnetId> {
851        self.find_subnets(SubnetKind::Application, None)
852    }
853
854    pub fn get_verified_app_subnets(&self) -> Vec<SubnetId> {
855        self.find_subnets(SubnetKind::VerifiedApplication, None)
856    }
857
858    pub fn get_benchmarking_app_subnets(&self) -> Vec<SubnetId> {
859        self.find_subnets(
860            SubnetKind::Application,
861            Some(SubnetInstructionConfig::Benchmarking),
862        )
863    }
864
865    pub fn get_bitcoin(&self) -> Option<SubnetId> {
866        self.find_subnet(SubnetKind::Bitcoin)
867    }
868
869    pub fn get_fiduciary(&self) -> Option<SubnetId> {
870        self.find_subnet(SubnetKind::Fiduciary)
871    }
872
873    pub fn get_ii(&self) -> Option<SubnetId> {
874        self.find_subnet(SubnetKind::II)
875    }
876
877    pub fn get_nns(&self) -> Option<SubnetId> {
878        self.find_subnet(SubnetKind::NNS)
879    }
880
881    pub fn get_sns(&self) -> Option<SubnetId> {
882        self.find_subnet(SubnetKind::SNS)
883    }
884
885    pub fn get_system_subnets(&self) -> Vec<SubnetId> {
886        self.find_subnets(SubnetKind::System, None)
887    }
888
889    fn find_subnets(
890        &self,
891        kind: SubnetKind,
892        instruction_config: Option<SubnetInstructionConfig>,
893    ) -> Vec<SubnetId> {
894        self.subnet_configs
895            .iter()
896            .filter(|(_, config)| {
897                config.subnet_kind == kind
898                    && instruction_config
899                        .as_ref()
900                        .map(|instruction_config| config.instruction_config == *instruction_config)
901                        .unwrap_or(true)
902            })
903            .map(|(id, _)| *id)
904            .collect()
905    }
906
907    fn find_subnet(&self, kind: SubnetKind) -> Option<SubnetId> {
908        self.subnet_configs
909            .iter()
910            .find(|(_, config)| config.subnet_kind == kind)
911            .map(|(id, _)| *id)
912    }
913}
914
915#[derive(
916    Clone, Serialize, Deserialize, Debug, Hash, Eq, PartialEq, Ord, PartialOrd, JsonSchema,
917)]
918pub enum CanisterHttpMethod {
919    GET,
920    POST,
921    HEAD,
922}
923
924#[derive(
925    Clone, Serialize, Deserialize, Debug, Hash, Eq, PartialEq, Ord, PartialOrd, JsonSchema,
926)]
927pub struct CanisterHttpHeader {
928    pub name: String,
929    pub value: String,
930}
931
932#[derive(Clone, Serialize, Deserialize, Debug, JsonSchema)]
933pub struct RawCanisterHttpRequest {
934    pub subnet_id: RawSubnetId,
935    pub request_id: u64,
936    pub http_method: CanisterHttpMethod,
937    pub url: String,
938    pub headers: Vec<CanisterHttpHeader>,
939    #[serde(deserialize_with = "base64::deserialize")]
940    #[serde(serialize_with = "base64::serialize")]
941    pub body: Vec<u8>,
942    pub max_response_bytes: Option<u64>,
943}
944
945#[derive(Clone, Serialize, Deserialize, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)]
946pub struct CanisterHttpRequest {
947    pub subnet_id: Principal,
948    pub request_id: u64,
949    pub http_method: CanisterHttpMethod,
950    pub url: String,
951    pub headers: Vec<CanisterHttpHeader>,
952    #[serde(deserialize_with = "base64::deserialize")]
953    #[serde(serialize_with = "base64::serialize")]
954    pub body: Vec<u8>,
955    pub max_response_bytes: Option<u64>,
956}
957
958impl From<RawCanisterHttpRequest> for CanisterHttpRequest {
959    fn from(raw_canister_http_request: RawCanisterHttpRequest) -> Self {
960        Self {
961            subnet_id: candid::Principal::from_slice(
962                &raw_canister_http_request.subnet_id.subnet_id,
963            ),
964            request_id: raw_canister_http_request.request_id,
965            http_method: raw_canister_http_request.http_method,
966            url: raw_canister_http_request.url,
967            headers: raw_canister_http_request.headers,
968            body: raw_canister_http_request.body,
969            max_response_bytes: raw_canister_http_request.max_response_bytes,
970        }
971    }
972}
973
974impl From<CanisterHttpRequest> for RawCanisterHttpRequest {
975    fn from(canister_http_request: CanisterHttpRequest) -> Self {
976        Self {
977            subnet_id: canister_http_request.subnet_id.into(),
978            request_id: canister_http_request.request_id,
979            http_method: canister_http_request.http_method,
980            url: canister_http_request.url,
981            headers: canister_http_request.headers,
982            body: canister_http_request.body,
983            max_response_bytes: canister_http_request.max_response_bytes,
984        }
985    }
986}
987
988#[derive(
989    Clone, Serialize, Deserialize, Debug, Hash, Eq, PartialEq, Ord, PartialOrd, JsonSchema,
990)]
991pub struct CanisterHttpReply {
992    pub status: u16,
993    pub headers: Vec<CanisterHttpHeader>,
994    #[serde(deserialize_with = "base64::deserialize")]
995    #[serde(serialize_with = "base64::serialize")]
996    pub body: Vec<u8>,
997}
998
999#[derive(
1000    Clone, Serialize, Deserialize, Debug, Hash, Eq, PartialEq, Ord, PartialOrd, JsonSchema,
1001)]
1002pub struct CanisterHttpReject {
1003    pub reject_code: u64,
1004    pub message: String,
1005}
1006
1007#[derive(
1008    Clone, Serialize, Deserialize, Debug, Hash, Eq, PartialEq, Ord, PartialOrd, JsonSchema,
1009)]
1010pub enum CanisterHttpResponse {
1011    CanisterHttpReply(CanisterHttpReply),
1012    CanisterHttpReject(CanisterHttpReject),
1013}
1014
1015#[derive(Clone, Serialize, Deserialize, Debug, JsonSchema)]
1016pub struct RawMockCanisterHttpResponse {
1017    pub subnet_id: RawSubnetId,
1018    pub request_id: u64,
1019    pub response: CanisterHttpResponse,
1020    pub additional_responses: Vec<CanisterHttpResponse>,
1021}
1022
1023#[derive(Clone, Serialize, Deserialize, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)]
1024pub struct MockCanisterHttpResponse {
1025    pub subnet_id: Principal,
1026    pub request_id: u64,
1027    pub response: CanisterHttpResponse,
1028    pub additional_responses: Vec<CanisterHttpResponse>,
1029}
1030
1031impl From<RawMockCanisterHttpResponse> for MockCanisterHttpResponse {
1032    fn from(raw_mock_canister_http_response: RawMockCanisterHttpResponse) -> Self {
1033        Self {
1034            subnet_id: candid::Principal::from_slice(
1035                &raw_mock_canister_http_response.subnet_id.subnet_id,
1036            ),
1037            request_id: raw_mock_canister_http_response.request_id,
1038            response: raw_mock_canister_http_response.response,
1039            additional_responses: raw_mock_canister_http_response.additional_responses,
1040        }
1041    }
1042}
1043
1044impl From<MockCanisterHttpResponse> for RawMockCanisterHttpResponse {
1045    fn from(mock_canister_http_response: MockCanisterHttpResponse) -> Self {
1046        Self {
1047            subnet_id: RawSubnetId {
1048                subnet_id: mock_canister_http_response.subnet_id.as_slice().to_vec(),
1049            },
1050            request_id: mock_canister_http_response.request_id,
1051            response: mock_canister_http_response.response,
1052            additional_responses: mock_canister_http_response.additional_responses,
1053        }
1054    }
1055}