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 configuration of an ICP feature resembling mainnet configuration as closely as possible.
572    #[default]
573    DefaultConfig,
574}
575
576/// Specifies ICP features enabled by deploying their corresponding system canisters
577/// when creating a PocketIC instance and keeping them up to date
578/// during the PocketIC instance lifetime.
579/// The subnets to which the corresponding system canisters are deployed must be empty,
580/// i.e., their corresponding field in `ExtendedSubnetConfigSet` must be `None`
581/// or `Some(config)` with `config.state_config = SubnetStateConfig::New`.
582/// An ICP feature is enabled if its `IcpFeaturesConfig` is provided, i.e.,
583/// if the corresponding field is not `None`.
584#[derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize, Default, JsonSchema)]
585pub struct IcpFeatures {
586    /// Deploys the NNS registry canister and keeps its content in sync with registry used internally by PocketIC.
587    /// Subnets: NNS.
588    pub registry: Option<IcpFeaturesConfig>,
589    /// Deploys the NNS cycles minting canister, sets ICP/XDR conversion rate, and keeps its subnet lists in sync with PocketIC topology.
590    /// 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).
591    /// Subnets: NNS.
592    pub cycles_minting: Option<IcpFeaturesConfig>,
593    /// Deploys the ICP ledger and index canisters and initializes the ICP account of the anonymous principal with 1,000,000,000 ICP.
594    /// Subnets: NNS.
595    pub icp_token: Option<IcpFeaturesConfig>,
596    /// Deploys the cycles ledger and index canisters and initializes the cycles account of the anonymous principal with 2^127 cycles.
597    /// Subnets: II.
598    pub cycles_token: Option<IcpFeaturesConfig>,
599    /// Deploys the NNS governance and root canisters and sets up an initial NNS neuron with 1 ICP stake.
600    /// The initial NNS neuron is controlled by the anonymous principal.
601    /// Subnets: NNS.
602    pub nns_governance: Option<IcpFeaturesConfig>,
603    /// Deploys the SNS-W and aggregator canisters, sets up the SNS subnet list in the SNS-W canister according to PocketIC topology,
604    /// and uploads the SNS canister WASMs to the SNS-W canister.
605    /// Subnets: NNS, SNS.
606    pub sns: Option<IcpFeaturesConfig>,
607    /// Deploys the Internet Identity canister.
608    /// Subnets: II.
609    pub ii: Option<IcpFeaturesConfig>,
610    /// Deploys the NNS frontend dapp. The HTTP gateway must be specified via `http_gateway_config` in `InstanceConfig`
611    /// and the ICP features `cycles_minting`, `icp_token`, `nns_governance`, `sns`, `ii` must all be enabled.
612    /// Subnets: NNS.
613    pub nns_ui: Option<IcpFeaturesConfig>,
614    /// Deploys the Bitcoin canister under the testnet canister ID `g4xu7-jiaaa-aaaan-aaaaq-cai` and configured for the regtest network.
615    /// Subnets: Bitcoin.
616    pub bitcoin: Option<IcpFeaturesConfig>,
617    /// Deploys the Dogecoin canister under the mainnet canister ID `gordg-fyaaa-aaaan-aaadq-cai` and configured for the regtest network.
618    /// Subnets: Bitcoin.
619    pub dogecoin: Option<IcpFeaturesConfig>,
620    /// Deploys the canister migration orchestrator canister.
621    /// Subnets: NNS.
622    pub canister_migration: Option<IcpFeaturesConfig>,
623}
624
625#[derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize, JsonSchema)]
626pub enum InitialTime {
627    /// Sets the initial timestamp of the new instance to the provided value which must be at least
628    /// - 10 May 2021 10:00:01 AM CEST if the `cycles_minting` feature is enabled in `icp_features`;
629    /// - 06 May 2021 21:17:10 CEST otherwise.
630    Timestamp(RawTime),
631    /// Configures the new instance to make progress automatically,
632    /// i.e., periodically update the time of the IC instance
633    /// to the real time and execute rounds on the subnets.
634    AutoProgress(AutoProgressConfig),
635}
636
637#[derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize, Default, JsonSchema)]
638pub enum IncompleteStateFlag {
639    #[default]
640    Disabled,
641    Enabled,
642}
643
644#[derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize, Default, JsonSchema)]
645pub struct InstanceConfig {
646    pub subnet_config_set: ExtendedSubnetConfigSet,
647    pub http_gateway_config: Option<InstanceHttpGatewayConfig>,
648    pub state_dir: Option<PathBuf>,
649    pub icp_config: Option<IcpConfig>,
650    pub log_level: Option<String>,
651    pub bitcoind_addr: Option<Vec<SocketAddr>>,
652    pub dogecoind_addr: Option<Vec<SocketAddr>>,
653    pub icp_features: Option<IcpFeatures>,
654    pub incomplete_state: Option<IncompleteStateFlag>,
655    pub initial_time: Option<InitialTime>,
656}
657
658#[derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize, Default, JsonSchema)]
659pub struct ExtendedSubnetConfigSet {
660    pub nns: Option<SubnetSpec>,
661    pub sns: Option<SubnetSpec>,
662    pub ii: Option<SubnetSpec>,
663    pub fiduciary: Option<SubnetSpec>,
664    pub bitcoin: Option<SubnetSpec>,
665    pub system: Vec<SubnetSpec>,
666    pub application: Vec<SubnetSpec>,
667    pub verified_application: Vec<SubnetSpec>,
668}
669
670/// Specifies various configurations for a subnet.
671#[derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize, JsonSchema)]
672pub struct SubnetSpec {
673    state_config: SubnetStateConfig,
674    instruction_config: SubnetInstructionConfig,
675}
676
677impl SubnetSpec {
678    pub fn with_state_dir(mut self, path: PathBuf) -> SubnetSpec {
679        self.state_config = SubnetStateConfig::FromPath(path);
680        self
681    }
682
683    pub fn with_benchmarking_instruction_config(mut self) -> SubnetSpec {
684        self.instruction_config = SubnetInstructionConfig::Benchmarking;
685        self
686    }
687
688    pub fn get_state_path(&self) -> Option<PathBuf> {
689        self.state_config.get_path()
690    }
691
692    pub fn get_instruction_config(&self) -> SubnetInstructionConfig {
693        self.instruction_config.clone()
694    }
695
696    pub fn is_supported(&self) -> bool {
697        match &self.state_config {
698            SubnetStateConfig::New => true,
699            SubnetStateConfig::FromPath(..) => true,
700            SubnetStateConfig::FromBlobStore(..) => false,
701        }
702    }
703}
704
705impl Default for SubnetSpec {
706    fn default() -> Self {
707        Self {
708            state_config: SubnetStateConfig::New,
709            instruction_config: SubnetInstructionConfig::Production,
710        }
711    }
712}
713
714/// Specifies instruction limits for canister execution on this subnet.
715#[derive(
716    Debug, Clone, Eq, Hash, PartialEq, Ord, PartialOrd, Serialize, Deserialize, JsonSchema,
717)]
718pub enum SubnetInstructionConfig {
719    /// Use default instruction limits as in production.
720    Production,
721    /// Use very high instruction limits useful for asymptotic canister benchmarking.
722    Benchmarking,
723}
724
725/// Specifies whether the subnet should be created from scratch or loaded
726/// from a path.
727#[derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize, JsonSchema)]
728pub enum SubnetStateConfig {
729    /// Create new subnet with empty state.
730    New,
731    /// Load existing subnet state from the given path.
732    /// The path must be on a filesystem accessible to the server process.
733    FromPath(PathBuf),
734    /// Load existing subnet state from blobstore. Needs to be uploaded first!
735    /// Not implemented!
736    FromBlobStore(BlobId),
737}
738
739impl SubnetStateConfig {
740    pub fn get_path(&self) -> Option<PathBuf> {
741        match self {
742            SubnetStateConfig::FromPath(path) => Some(path.clone()),
743            SubnetStateConfig::FromBlobStore(_) => None,
744            SubnetStateConfig::New => None,
745        }
746    }
747}
748
749impl ExtendedSubnetConfigSet {
750    // Return the configured named subnets in order.
751    #[allow(clippy::type_complexity)]
752    pub fn get_named(&self) -> Vec<(SubnetKind, Option<PathBuf>, SubnetInstructionConfig)> {
753        use SubnetKind::*;
754        vec![
755            (self.nns.clone(), NNS),
756            (self.sns.clone(), SNS),
757            (self.ii.clone(), II),
758            (self.fiduciary.clone(), Fiduciary),
759            (self.bitcoin.clone(), Bitcoin),
760        ]
761        .into_iter()
762        .filter(|(mb, _)| mb.is_some())
763        .map(|(mb, kind)| {
764            let spec = mb.unwrap();
765            (kind, spec.get_state_path(), spec.get_instruction_config())
766        })
767        .collect()
768    }
769
770    pub fn validate(&self) -> Result<(), String> {
771        if !self.system.is_empty()
772            || !self.application.is_empty()
773            || !self.verified_application.is_empty()
774            || self.nns.is_some()
775            || self.sns.is_some()
776            || self.ii.is_some()
777            || self.fiduciary.is_some()
778            || self.bitcoin.is_some()
779        {
780            return Ok(());
781        }
782        Err("ExtendedSubnetConfigSet must contain at least one subnet".to_owned())
783    }
784
785    pub fn try_with_icp_features(mut self, icp_features: &IcpFeatures) -> Result<Self, String> {
786        let check_empty_subnet = |subnet: &Option<SubnetSpec>, subnet_desc, icp_feature| {
787            if let Some(config) = subnet
788                && !matches!(config.state_config, SubnetStateConfig::New)
789            {
790                return Err(format!(
791                    "The {subnet_desc} subnet must be empty when specifying the `{icp_feature}` ICP feature."
792                ));
793            }
794            Ok(())
795        };
796        // using `let IcpFeatures { }` with explicit field names
797        // to force an update after adding a new field to `IcpFeatures`
798        let IcpFeatures {
799            registry,
800            cycles_minting,
801            icp_token,
802            cycles_token,
803            nns_governance,
804            sns,
805            ii,
806            nns_ui,
807            bitcoin,
808            dogecoin,
809            canister_migration,
810        } = icp_features;
811        // NNS canisters
812        for (flag, icp_feature_str) in [
813            (registry, "registry"),
814            (cycles_minting, "cycles_minting"),
815            (icp_token, "icp_token"),
816            (nns_governance, "nns_governance"),
817            (sns, "sns"),
818            (nns_ui, "nns_ui"),
819            (canister_migration, "canister_migration"),
820        ] {
821            if flag.is_some() {
822                check_empty_subnet(&self.nns, "NNS", icp_feature_str)?;
823                self.nns = Some(self.nns.unwrap_or_default());
824            }
825        }
826        // canisters on the II subnet
827        for (flag, icp_feature_str) in [(cycles_token, "cycles_token"), (ii, "ii")] {
828            if flag.is_some() {
829                check_empty_subnet(&self.ii, "II", icp_feature_str)?;
830                self.ii = Some(self.ii.unwrap_or_default());
831            }
832        }
833        // canisters on the SNS subnet
834        for (flag, icp_feature_str) in [(sns, "sns")] {
835            if flag.is_some() {
836                check_empty_subnet(&self.sns, "SNS", icp_feature_str)?;
837                self.sns = Some(self.sns.unwrap_or_default());
838            }
839        }
840        // canisters on the Bitcoin subnet
841        for (flag, icp_feature_str) in [(bitcoin, "bitcoin"), (dogecoin, "dogecoin")] {
842            if flag.is_some() {
843                check_empty_subnet(&self.bitcoin, "Bitcoin", icp_feature_str)?;
844                self.bitcoin = Some(self.bitcoin.unwrap_or_default());
845            }
846        }
847        Ok(self)
848    }
849}
850
851/// Configuration details for a subnet, returned by PocketIc server
852#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize, JsonSchema)]
853pub struct SubnetConfig {
854    pub subnet_kind: SubnetKind,
855    pub subnet_seed: [u8; 32],
856    /// Instruction limits for canister execution on this subnet.
857    pub instruction_config: SubnetInstructionConfig,
858    /// Some mainnet subnets have several disjunct canister ranges.
859    pub canister_ranges: Vec<CanisterIdRange>,
860}
861
862#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize, JsonSchema)]
863pub struct CanisterIdRange {
864    pub start: RawCanisterId,
865    pub end: RawCanisterId,
866}
867
868impl CanisterIdRange {
869    fn contains(&self, canister_id: Principal) -> bool {
870        Principal::from_slice(&self.start.canister_id) <= canister_id
871            && canister_id <= Principal::from_slice(&self.end.canister_id)
872    }
873}
874
875#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize, JsonSchema)]
876pub struct Topology {
877    pub subnet_configs: BTreeMap<SubnetId, SubnetConfig>,
878    pub default_effective_canister_id: RawCanisterId,
879}
880
881impl Topology {
882    pub fn get_subnet(&self, canister_id: Principal) -> Option<SubnetId> {
883        self.subnet_configs
884            .iter()
885            .find(|(_, config)| {
886                config
887                    .canister_ranges
888                    .iter()
889                    .any(|r| r.contains(canister_id))
890            })
891            .map(|(subnet_id, _)| subnet_id)
892            .copied()
893    }
894
895    pub fn get_app_subnets(&self) -> Vec<SubnetId> {
896        self.find_subnets(SubnetKind::Application, None)
897    }
898
899    pub fn get_verified_app_subnets(&self) -> Vec<SubnetId> {
900        self.find_subnets(SubnetKind::VerifiedApplication, None)
901    }
902
903    pub fn get_benchmarking_app_subnets(&self) -> Vec<SubnetId> {
904        self.find_subnets(
905            SubnetKind::Application,
906            Some(SubnetInstructionConfig::Benchmarking),
907        )
908    }
909
910    pub fn get_bitcoin(&self) -> Option<SubnetId> {
911        self.find_subnet(SubnetKind::Bitcoin)
912    }
913
914    pub fn get_fiduciary(&self) -> Option<SubnetId> {
915        self.find_subnet(SubnetKind::Fiduciary)
916    }
917
918    pub fn get_ii(&self) -> Option<SubnetId> {
919        self.find_subnet(SubnetKind::II)
920    }
921
922    pub fn get_nns(&self) -> Option<SubnetId> {
923        self.find_subnet(SubnetKind::NNS)
924    }
925
926    pub fn get_sns(&self) -> Option<SubnetId> {
927        self.find_subnet(SubnetKind::SNS)
928    }
929
930    pub fn get_system_subnets(&self) -> Vec<SubnetId> {
931        self.find_subnets(SubnetKind::System, None)
932    }
933
934    fn find_subnets(
935        &self,
936        kind: SubnetKind,
937        instruction_config: Option<SubnetInstructionConfig>,
938    ) -> Vec<SubnetId> {
939        self.subnet_configs
940            .iter()
941            .filter(|(_, config)| {
942                config.subnet_kind == kind
943                    && instruction_config
944                        .as_ref()
945                        .map(|instruction_config| config.instruction_config == *instruction_config)
946                        .unwrap_or(true)
947            })
948            .map(|(id, _)| *id)
949            .collect()
950    }
951
952    fn find_subnet(&self, kind: SubnetKind) -> Option<SubnetId> {
953        self.subnet_configs
954            .iter()
955            .find(|(_, config)| config.subnet_kind == kind)
956            .map(|(id, _)| *id)
957    }
958}
959
960#[derive(
961    Clone, Serialize, Deserialize, Debug, Hash, Eq, PartialEq, Ord, PartialOrd, JsonSchema,
962)]
963pub enum CanisterHttpMethod {
964    GET,
965    POST,
966    HEAD,
967}
968
969#[derive(
970    Clone, Serialize, Deserialize, Debug, Hash, Eq, PartialEq, Ord, PartialOrd, JsonSchema,
971)]
972pub struct CanisterHttpHeader {
973    pub name: String,
974    pub value: String,
975}
976
977#[derive(Clone, Serialize, Deserialize, Debug, JsonSchema)]
978pub struct RawCanisterHttpRequest {
979    pub subnet_id: RawSubnetId,
980    pub request_id: u64,
981    pub http_method: CanisterHttpMethod,
982    pub url: String,
983    pub headers: Vec<CanisterHttpHeader>,
984    #[serde(deserialize_with = "base64::deserialize")]
985    #[serde(serialize_with = "base64::serialize")]
986    pub body: Vec<u8>,
987    pub max_response_bytes: Option<u64>,
988}
989
990#[derive(Clone, Serialize, Deserialize, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)]
991pub struct CanisterHttpRequest {
992    pub subnet_id: Principal,
993    pub request_id: u64,
994    pub http_method: CanisterHttpMethod,
995    pub url: String,
996    pub headers: Vec<CanisterHttpHeader>,
997    #[serde(deserialize_with = "base64::deserialize")]
998    #[serde(serialize_with = "base64::serialize")]
999    pub body: Vec<u8>,
1000    pub max_response_bytes: Option<u64>,
1001}
1002
1003impl From<RawCanisterHttpRequest> for CanisterHttpRequest {
1004    fn from(raw_canister_http_request: RawCanisterHttpRequest) -> Self {
1005        Self {
1006            subnet_id: candid::Principal::from_slice(
1007                &raw_canister_http_request.subnet_id.subnet_id,
1008            ),
1009            request_id: raw_canister_http_request.request_id,
1010            http_method: raw_canister_http_request.http_method,
1011            url: raw_canister_http_request.url,
1012            headers: raw_canister_http_request.headers,
1013            body: raw_canister_http_request.body,
1014            max_response_bytes: raw_canister_http_request.max_response_bytes,
1015        }
1016    }
1017}
1018
1019impl From<CanisterHttpRequest> for RawCanisterHttpRequest {
1020    fn from(canister_http_request: CanisterHttpRequest) -> Self {
1021        Self {
1022            subnet_id: canister_http_request.subnet_id.into(),
1023            request_id: canister_http_request.request_id,
1024            http_method: canister_http_request.http_method,
1025            url: canister_http_request.url,
1026            headers: canister_http_request.headers,
1027            body: canister_http_request.body,
1028            max_response_bytes: canister_http_request.max_response_bytes,
1029        }
1030    }
1031}
1032
1033#[derive(
1034    Clone, Serialize, Deserialize, Debug, Hash, Eq, PartialEq, Ord, PartialOrd, JsonSchema,
1035)]
1036pub struct CanisterHttpReply {
1037    pub status: u16,
1038    pub headers: Vec<CanisterHttpHeader>,
1039    #[serde(deserialize_with = "base64::deserialize")]
1040    #[serde(serialize_with = "base64::serialize")]
1041    pub body: Vec<u8>,
1042}
1043
1044#[derive(
1045    Clone, Serialize, Deserialize, Debug, Hash, Eq, PartialEq, Ord, PartialOrd, JsonSchema,
1046)]
1047pub struct CanisterHttpReject {
1048    pub reject_code: u64,
1049    pub message: String,
1050}
1051
1052#[derive(
1053    Clone, Serialize, Deserialize, Debug, Hash, Eq, PartialEq, Ord, PartialOrd, JsonSchema,
1054)]
1055pub enum CanisterHttpResponse {
1056    CanisterHttpReply(CanisterHttpReply),
1057    CanisterHttpReject(CanisterHttpReject),
1058}
1059
1060#[derive(Clone, Serialize, Deserialize, Debug, JsonSchema)]
1061pub struct RawMockCanisterHttpResponse {
1062    pub subnet_id: RawSubnetId,
1063    pub request_id: u64,
1064    pub response: CanisterHttpResponse,
1065    pub additional_responses: Vec<CanisterHttpResponse>,
1066}
1067
1068#[derive(Clone, Serialize, Deserialize, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)]
1069pub struct MockCanisterHttpResponse {
1070    pub subnet_id: Principal,
1071    pub request_id: u64,
1072    pub response: CanisterHttpResponse,
1073    pub additional_responses: Vec<CanisterHttpResponse>,
1074}
1075
1076impl From<RawMockCanisterHttpResponse> for MockCanisterHttpResponse {
1077    fn from(raw_mock_canister_http_response: RawMockCanisterHttpResponse) -> Self {
1078        Self {
1079            subnet_id: candid::Principal::from_slice(
1080                &raw_mock_canister_http_response.subnet_id.subnet_id,
1081            ),
1082            request_id: raw_mock_canister_http_response.request_id,
1083            response: raw_mock_canister_http_response.response,
1084            additional_responses: raw_mock_canister_http_response.additional_responses,
1085        }
1086    }
1087}
1088
1089impl From<MockCanisterHttpResponse> for RawMockCanisterHttpResponse {
1090    fn from(mock_canister_http_response: MockCanisterHttpResponse) -> Self {
1091        Self {
1092            subnet_id: RawSubnetId {
1093                subnet_id: mock_canister_http_response.subnet_id.as_slice().to_vec(),
1094            },
1095            request_id: mock_canister_http_response.request_id,
1096            response: mock_canister_http_response.response,
1097            additional_responses: mock_canister_http_response.additional_responses,
1098        }
1099    }
1100}
1101
1102#[derive(Clone, Serialize, Deserialize, Debug, JsonSchema)]
1103pub struct RawCanisterSnapshotDownload {
1104    pub sender: RawPrincipalId,
1105    pub canister_id: RawCanisterId,
1106    #[serde(deserialize_with = "base64::deserialize")]
1107    #[serde(serialize_with = "base64::serialize")]
1108    pub snapshot_id: Vec<u8>,
1109    pub snapshot_dir: PathBuf,
1110}
1111
1112#[derive(Clone, Serialize, Deserialize, Debug, JsonSchema)]
1113pub struct RawCanisterSnapshotUpload {
1114    pub sender: RawPrincipalId,
1115    pub canister_id: RawCanisterId,
1116    pub replace_snapshot: Option<RawCanisterSnapshotId>,
1117    pub snapshot_dir: PathBuf,
1118}
1119
1120#[derive(Clone, Serialize, Deserialize, Debug, JsonSchema)]
1121pub struct RawCanisterSnapshotId {
1122    #[serde(deserialize_with = "base64::deserialize")]
1123    #[serde(serialize_with = "base64::serialize")]
1124    pub snapshot_id: Vec<u8>,
1125}