Skip to main content

ic_testkit/pocket_ic/common/
rest.rs

1// Copyright DFINITY Foundation. Licensed under Apache-2.0.
2//! The PocketIC server and the PocketIc library interface with HTTP/JSON.
3//! The types in this module are used to serialize and deserialize data
4//! from and to JSON, and are used by both crates.
5
6use crate::pocket_ic::RejectResponse;
7use candid::Principal;
8use hex;
9use schemars::JsonSchema;
10use serde::{Deserialize, Serialize};
11use std::collections::BTreeMap;
12use std::net::SocketAddr;
13use std::path::PathBuf;
14use strum::IntoEnumIterator;
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    pub domain_custom_provider_local_file: Option<String>,
43}
44
45#[derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize, JsonSchema)]
46pub struct HttpGatewayConfig {
47    pub ip_addr: Option<String>,
48    pub port: Option<u16>,
49    pub forward_to: HttpGatewayBackend,
50    pub domains: Option<Vec<String>>,
51    pub https_config: Option<HttpsConfig>,
52    pub domain_custom_provider_local_file: Option<String>,
53}
54
55#[derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize, JsonSchema)]
56pub struct HttpGatewayDetails {
57    pub instance_id: InstanceId,
58    pub port: u16,
59    pub forward_to: HttpGatewayBackend,
60    pub domains: Option<Vec<String>>,
61    pub https_config: Option<HttpsConfig>,
62}
63
64#[derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize, JsonSchema)]
65pub struct HttpGatewayInfo {
66    pub instance_id: InstanceId,
67    pub port: u16,
68}
69
70#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
71pub enum CreateHttpGatewayResponse {
72    Created(HttpGatewayInfo),
73    Error { message: String },
74}
75
76#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
77pub enum CreateInstanceResponse {
78    Created {
79        instance_id: InstanceId,
80        topology: Topology,
81        http_gateway_info: Option<HttpGatewayInfo>,
82    },
83    Error {
84        message: String,
85    },
86}
87
88#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq, Serialize, Deserialize, JsonSchema)]
89pub struct RawTime {
90    pub nanos_since_epoch: u64,
91}
92
93/// Relevant for calls to the management canister. If a subnet ID is
94/// provided, the call will be sent to the management canister of that subnet.
95/// If a canister ID is provided, the call will be sent to the management
96/// canister of the subnet where the canister is on.
97/// If None, the call will be sent to any management canister.
98#[derive(Clone, Serialize, Deserialize, Debug, JsonSchema, PartialEq, Eq, Hash)]
99pub enum RawEffectivePrincipal {
100    None,
101    SubnetId(
102        #[serde(deserialize_with = "base64::deserialize")]
103        #[serde(serialize_with = "base64::serialize")]
104        Vec<u8>,
105    ),
106    CanisterId(
107        #[serde(deserialize_with = "base64::deserialize")]
108        #[serde(serialize_with = "base64::serialize")]
109        Vec<u8>,
110    ),
111}
112
113impl std::fmt::Display for RawEffectivePrincipal {
114    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
115        match self {
116            RawEffectivePrincipal::None => write!(f, "None"),
117            RawEffectivePrincipal::SubnetId(subnet_id) => {
118                let principal = Principal::from_slice(subnet_id);
119                write!(f, "SubnetId({principal})")
120            }
121            RawEffectivePrincipal::CanisterId(canister_id) => {
122                let principal = Principal::from_slice(canister_id);
123                write!(f, "CanisterId({principal})")
124            }
125        }
126    }
127}
128
129#[derive(Clone, Serialize, Deserialize, Debug, JsonSchema)]
130pub struct RawMessageId {
131    pub effective_principal: RawEffectivePrincipal,
132    #[serde(deserialize_with = "base64::deserialize")]
133    #[serde(serialize_with = "base64::serialize")]
134    pub message_id: Vec<u8>,
135}
136
137#[derive(Clone, Serialize, Deserialize, Debug, JsonSchema)]
138pub struct RawIngressStatusArgs {
139    pub raw_message_id: RawMessageId,
140    pub raw_caller: Option<RawPrincipalId>,
141}
142
143#[derive(Clone, Serialize, Deserialize, Debug, JsonSchema)]
144pub struct RawSenderInfo {
145    #[serde(deserialize_with = "base64::deserialize")]
146    #[serde(serialize_with = "base64::serialize")]
147    pub info: Vec<u8>,
148    #[serde(deserialize_with = "base64::deserialize")]
149    #[serde(serialize_with = "base64::serialize")]
150    pub signer: Vec<u8>,
151}
152
153#[derive(Clone, Serialize, Deserialize, Debug, JsonSchema)]
154pub struct RawCanisterCall {
155    #[serde(deserialize_with = "base64::deserialize")]
156    #[serde(serialize_with = "base64::serialize")]
157    pub sender: Vec<u8>,
158    #[serde(deserialize_with = "base64::deserialize")]
159    #[serde(serialize_with = "base64::serialize")]
160    pub canister_id: Vec<u8>,
161    pub effective_principal: RawEffectivePrincipal,
162    pub method: String,
163    #[serde(deserialize_with = "base64::deserialize")]
164    #[serde(serialize_with = "base64::serialize")]
165    pub payload: Vec<u8>,
166    #[serde(skip_serializing_if = "Option::is_none")]
167    pub sender_info: Option<RawSenderInfo>,
168}
169
170#[derive(Clone, Serialize, Deserialize, Debug, JsonSchema)]
171pub enum RawCanisterResult {
172    Ok(
173        #[serde(deserialize_with = "base64::deserialize")]
174        #[serde(serialize_with = "base64::serialize")]
175        Vec<u8>,
176    ),
177    Err(RejectResponse),
178}
179
180impl From<Result<Vec<u8>, RejectResponse>> for RawCanisterResult {
181    fn from(result: Result<Vec<u8>, RejectResponse>) -> Self {
182        match result {
183            Ok(data) => RawCanisterResult::Ok(data),
184            Err(reject_response) => RawCanisterResult::Err(reject_response),
185        }
186    }
187}
188
189impl From<RawCanisterResult> for Result<Vec<u8>, RejectResponse> {
190    fn from(result: RawCanisterResult) -> Self {
191        match result {
192            RawCanisterResult::Ok(data) => Ok(data),
193            RawCanisterResult::Err(reject_response) => Err(reject_response),
194        }
195    }
196}
197
198#[derive(Clone, Serialize, Deserialize, Debug, JsonSchema)]
199pub struct RawSetStableMemory {
200    #[serde(deserialize_with = "base64::deserialize")]
201    #[serde(serialize_with = "base64::serialize")]
202    pub canister_id: Vec<u8>,
203    pub blob_id: BlobId,
204}
205
206#[derive(Clone, Serialize, Deserialize, Debug, JsonSchema)]
207pub struct RawStableMemory {
208    #[serde(deserialize_with = "base64::deserialize")]
209    #[serde(serialize_with = "base64::serialize")]
210    pub blob: Vec<u8>,
211}
212
213#[derive(Clone, Serialize, Deserialize, Debug, JsonSchema)]
214pub struct ApiError {
215    pub(crate) message: String,
216}
217
218#[derive(Clone, Serialize, Deserialize, Debug, JsonSchema)]
219pub struct StartedOrBusyResponse {
220    pub state_label: String,
221    pub op_id: String,
222}
223
224#[derive(Clone, Serialize, Deserialize, Debug, JsonSchema)]
225#[serde(untagged)]
226pub enum ApiResponse<T> {
227    Success(T),
228    Busy { state_label: String, op_id: String },
229    Started { state_label: String, op_id: String },
230    Error { message: String },
231}
232
233#[derive(Clone, Serialize, Deserialize, Debug, JsonSchema)]
234pub struct RawAddCycles {
235    #[serde(deserialize_with = "base64::deserialize")]
236    #[serde(serialize_with = "base64::serialize")]
237    pub canister_id: Vec<u8>,
238    pub amount: u128,
239}
240
241#[derive(Clone, Serialize, Deserialize, Debug, JsonSchema)]
242pub struct RawCycles {
243    pub cycles: u128,
244}
245
246#[derive(
247    Clone, Serialize, Hash, Eq, PartialEq, Ord, PartialOrd, Deserialize, Debug, JsonSchema,
248)]
249pub struct RawPrincipalId {
250    // raw bytes of the principal
251    #[serde(deserialize_with = "base64::deserialize")]
252    #[serde(serialize_with = "base64::serialize")]
253    pub principal_id: Vec<u8>,
254}
255
256impl From<Principal> for RawPrincipalId {
257    fn from(principal: Principal) -> Self {
258        Self {
259            principal_id: principal.as_slice().to_vec(),
260        }
261    }
262}
263
264impl From<RawPrincipalId> for Principal {
265    fn from(raw_principal_id: RawPrincipalId) -> Self {
266        Principal::from_slice(&raw_principal_id.principal_id)
267    }
268}
269
270#[derive(Clone, Serialize, Eq, PartialEq, Ord, PartialOrd, Deserialize, Debug, JsonSchema)]
271pub struct RawCanisterId {
272    // raw bytes of the principal
273    #[serde(deserialize_with = "base64::deserialize")]
274    #[serde(serialize_with = "base64::serialize")]
275    pub canister_id: Vec<u8>,
276}
277
278impl From<Principal> for RawCanisterId {
279    fn from(principal: Principal) -> Self {
280        Self {
281            canister_id: principal.as_slice().to_vec(),
282        }
283    }
284}
285
286impl From<RawCanisterId> for Principal {
287    fn from(raw_canister_id: RawCanisterId) -> Self {
288        Principal::from_slice(&raw_canister_id.canister_id)
289    }
290}
291
292#[derive(Clone, Serialize, Deserialize, Debug, JsonSchema, PartialEq, Eq, Hash)]
293pub struct RawSubnetId {
294    #[serde(deserialize_with = "base64::deserialize")]
295    #[serde(serialize_with = "base64::serialize")]
296    pub subnet_id: Vec<u8>,
297}
298
299pub type SubnetId = Principal;
300
301impl From<Principal> for RawSubnetId {
302    fn from(principal: Principal) -> Self {
303        Self {
304            subnet_id: principal.as_slice().to_vec(),
305        }
306    }
307}
308
309impl From<RawSubnetId> for Principal {
310    fn from(val: RawSubnetId) -> Self {
311        Principal::from_slice(&val.subnet_id)
312    }
313}
314
315#[derive(
316    Clone, Serialize, Deserialize, Debug, JsonSchema, PartialEq, Eq, PartialOrd, Ord, Hash,
317)]
318pub struct RawNodeId {
319    #[serde(deserialize_with = "base64::deserialize")]
320    #[serde(serialize_with = "base64::serialize")]
321    pub node_id: Vec<u8>,
322}
323
324impl From<RawNodeId> for Principal {
325    fn from(val: RawNodeId) -> Self {
326        Principal::from_slice(&val.node_id)
327    }
328}
329
330impl From<Principal> for RawNodeId {
331    fn from(principal: Principal) -> Self {
332        Self {
333            node_id: principal.as_slice().to_vec(),
334        }
335    }
336}
337
338#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, JsonSchema, Default)]
339pub struct RawTickConfigs {
340    pub blockmakers: Option<Vec<RawSubnetBlockmakers>>,
341}
342
343#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, JsonSchema)]
344pub struct RawSubnetBlockmakers {
345    pub subnet: RawSubnetId,
346    pub blockmaker: RawNodeId,
347    pub failed_blockmakers: Vec<RawNodeId>,
348}
349
350#[derive(Serialize, Deserialize, JsonSchema)]
351pub struct RawVerifyCanisterSigArg {
352    #[serde(deserialize_with = "base64::deserialize")]
353    #[serde(serialize_with = "base64::serialize")]
354    pub msg: Vec<u8>,
355    #[serde(deserialize_with = "base64::deserialize")]
356    #[serde(serialize_with = "base64::serialize")]
357    pub sig: Vec<u8>,
358    #[serde(deserialize_with = "base64::deserialize")]
359    #[serde(serialize_with = "base64::serialize")]
360    pub pubkey: Vec<u8>,
361    #[serde(deserialize_with = "base64::deserialize")]
362    #[serde(serialize_with = "base64::serialize")]
363    pub root_pubkey: Vec<u8>,
364}
365
366#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize, JsonSchema)]
367pub struct BlobId(
368    #[serde(deserialize_with = "base64::deserialize")]
369    #[serde(serialize_with = "base64::serialize")]
370    pub Vec<u8>,
371);
372
373impl std::fmt::Display for BlobId {
374    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
375        write!(f, "BlobId{{{}}}", hex::encode(self.0.clone()))
376    }
377}
378
379#[derive(Clone, Debug)]
380pub struct BinaryBlob {
381    pub data: Vec<u8>,
382    pub compression: BlobCompression,
383}
384
385#[derive(Clone, Copy, Debug, Eq, PartialEq, JsonSchema)]
386pub enum BlobCompression {
387    Gzip,
388    NoCompression,
389}
390
391// By default, serde serializes Vec<u8> to a list of numbers, which is inefficient.
392// This enables serializing Vec<u8> to a compact base64 representation.
393pub mod base64 {
394    use ::base64::prelude::*;
395    use serde::{Deserialize, Serialize};
396    use serde::{Deserializer, Serializer};
397
398    pub fn serialize<S: Serializer>(v: &Vec<u8>, s: S) -> Result<S::Ok, S::Error> {
399        let base64 = BASE64_STANDARD.encode(v);
400        String::serialize(&base64, s)
401    }
402
403    pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<Vec<u8>, D::Error> {
404        let base64 = String::deserialize(d)?;
405        BASE64_STANDARD
406            .decode(base64.as_bytes())
407            .map_err(serde::de::Error::custom)
408    }
409}
410
411// ================================================================================================================= //
412
413#[derive(
414    Debug,
415    Clone,
416    Copy,
417    Eq,
418    Hash,
419    PartialEq,
420    Ord,
421    PartialOrd,
422    Serialize,
423    Deserialize,
424    JsonSchema,
425    EnumIter,
426)]
427pub enum SubnetKind {
428    Application,
429    Bitcoin,
430    CloudEngine,
431    Fiduciary,
432    II,
433    NNS,
434    SNS,
435    System,
436    TestThresholdKeys,
437    VerifiedApplication,
438}
439
440impl SubnetKind {
441    pub fn is_named(self) -> bool {
442        NamedSubnet::try_from(self).is_ok()
443    }
444}
445
446/// Named subnets have a fixed canister ID range on the IC mainnet and at most one
447/// instance per PocketIC instance.
448#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq, Serialize, Deserialize, JsonSchema, EnumIter)]
449pub enum NamedSubnet {
450    NNS,
451    SNS,
452    II,
453    Fiduciary,
454    Bitcoin,
455    TestThresholdKeys,
456}
457
458impl From<NamedSubnet> for SubnetKind {
459    fn from(named: NamedSubnet) -> Self {
460        match named {
461            NamedSubnet::NNS => SubnetKind::NNS,
462            NamedSubnet::SNS => SubnetKind::SNS,
463            NamedSubnet::II => SubnetKind::II,
464            NamedSubnet::Fiduciary => SubnetKind::Fiduciary,
465            NamedSubnet::Bitcoin => SubnetKind::Bitcoin,
466            NamedSubnet::TestThresholdKeys => SubnetKind::TestThresholdKeys,
467        }
468    }
469}
470
471/// The exhaustive match over `SubnetKind` enforces structural consistency: adding a new
472/// `SubnetKind` variant is a compile error until it is explicitly placed in either the named
473/// or unnamed arm here and in `From<NamedSubnet> for SubnetKind`.
474impl TryFrom<SubnetKind> for NamedSubnet {
475    type Error = ();
476    fn try_from(kind: SubnetKind) -> Result<Self, Self::Error> {
477        match kind {
478            SubnetKind::NNS => Ok(NamedSubnet::NNS),
479            SubnetKind::SNS => Ok(NamedSubnet::SNS),
480            SubnetKind::II => Ok(NamedSubnet::II),
481            SubnetKind::Fiduciary => Ok(NamedSubnet::Fiduciary),
482            SubnetKind::Bitcoin => Ok(NamedSubnet::Bitcoin),
483            SubnetKind::TestThresholdKeys => Ok(NamedSubnet::TestThresholdKeys),
484            SubnetKind::Application
485            | SubnetKind::CloudEngine
486            | SubnetKind::System
487            | SubnetKind::VerifiedApplication => Err(()),
488        }
489    }
490}
491
492/// This represents which named subnets the user wants to create, and how
493/// many of the general app/system subnets, which are indistinguishable.
494#[derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize, Default, JsonSchema)]
495pub struct SubnetConfigSet {
496    pub nns: bool,
497    pub sns: bool,
498    pub ii: bool,
499    pub fiduciary: bool,
500    pub bitcoin: bool,
501    pub test_threshold_keys: bool,
502    pub system: usize,
503    pub application: usize,
504    pub cloud_engine: usize,
505    pub verified_application: usize,
506}
507
508impl SubnetConfigSet {
509    pub fn validate(&self) -> Result<(), String> {
510        if self.system > 0
511            || self.application > 0
512            || self.cloud_engine > 0
513            || self.verified_application > 0
514            || self.nns
515            || self.sns
516            || self.ii
517            || self.fiduciary
518            || self.bitcoin
519            || self.test_threshold_keys
520        {
521            return Ok(());
522        }
523        Err("SubnetConfigSet must contain at least one subnet".to_owned())
524    }
525}
526
527impl From<SubnetConfigSet> for ExtendedSubnetConfigSet {
528    fn from(
529        SubnetConfigSet {
530            nns,
531            sns,
532            ii,
533            fiduciary: fid,
534            bitcoin,
535            test_threshold_keys,
536            system,
537            application,
538            cloud_engine,
539            verified_application,
540        }: SubnetConfigSet,
541    ) -> Self {
542        ExtendedSubnetConfigSet {
543            nns: if nns {
544                Some(SubnetSpec::default())
545            } else {
546                None
547            },
548            sns: if sns {
549                Some(SubnetSpec::default())
550            } else {
551                None
552            },
553            ii: if ii {
554                Some(SubnetSpec::default())
555            } else {
556                None
557            },
558            fiduciary: if fid {
559                Some(SubnetSpec::default())
560            } else {
561                None
562            },
563            bitcoin: if bitcoin {
564                Some(SubnetSpec::default())
565            } else {
566                None
567            },
568            test_threshold_keys: if test_threshold_keys {
569                Some(SubnetSpec::default())
570            } else {
571                None
572            },
573            system: vec![SubnetSpec::default(); system],
574            application: vec![SubnetSpec::default(); application],
575            cloud_engine: vec![SubnetSpec::default(); cloud_engine],
576            verified_application: vec![SubnetSpec::default(); verified_application],
577        }
578    }
579}
580
581#[derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize, JsonSchema)]
582pub enum IcpConfigFlag {
583    Disabled,
584    Enabled,
585}
586
587/// Specifies ICP config of this instance.
588#[derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize, Default, JsonSchema)]
589pub struct IcpConfig {
590    /// Beta features (disabled on the ICP mainnet).
591    pub beta_features: Option<IcpConfigFlag>,
592    /// Limits on function name length in canister WASM (enabled on the ICP mainnet).
593    pub function_name_length_limits: Option<IcpConfigFlag>,
594    /// Rate-limiting of canister execution (enabled on the ICP mainnet).
595    /// Canister execution refers to instructions and memory writes here.
596    pub canister_execution_rate_limiting: Option<IcpConfigFlag>,
597}
598
599#[derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize, Default, JsonSchema)]
600pub enum IcpFeaturesConfig {
601    /// Default configuration of an ICP feature resembling mainnet configuration as closely as possible.
602    #[default]
603    DefaultConfig,
604}
605
606/// Specifies ICP features enabled by deploying their corresponding system canisters
607/// when creating a PocketIC instance and keeping them up to date
608/// during the PocketIC instance lifetime.
609/// The subnets to which the corresponding system canisters are deployed must be empty,
610/// i.e., their corresponding field in `ExtendedSubnetConfigSet` must be `None`
611/// or `Some(config)` with `config.state_config = SubnetStateConfig::New`.
612/// An ICP feature is enabled if its `IcpFeaturesConfig` is provided, i.e.,
613/// if the corresponding field is not `None`.
614#[derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize, Default, JsonSchema)]
615pub struct IcpFeatures {
616    /// Deploys the NNS registry canister and keeps its content in sync with registry used internally by PocketIC.
617    /// Subnets: NNS.
618    pub registry: Option<IcpFeaturesConfig>,
619    /// Deploys the NNS cycles minting canister, sets ICP/XDR conversion rate, and keeps its subnet lists in sync with PocketIC topology.
620    /// 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).
621    /// Subnets: NNS.
622    pub cycles_minting: Option<IcpFeaturesConfig>,
623    /// Deploys the ICP ledger and index canisters and initializes the ICP account of the anonymous principal with 1,000,000,000 ICP.
624    /// Subnets: NNS.
625    pub icp_token: Option<IcpFeaturesConfig>,
626    /// Deploys the cycles ledger and index canisters and initializes the cycles account of the anonymous principal with 2^127 cycles.
627    /// Subnets: II.
628    pub cycles_token: Option<IcpFeaturesConfig>,
629    /// Deploys the NNS governance and root canisters and sets up an initial NNS neuron with 1 ICP stake.
630    /// The initial NNS neuron is controlled by the anonymous principal.
631    /// Subnets: NNS.
632    pub nns_governance: Option<IcpFeaturesConfig>,
633    /// Deploys the SNS-W and aggregator canisters, sets up the SNS subnet list in the SNS-W canister according to PocketIC topology,
634    /// and uploads the SNS canister WASMs to the SNS-W canister.
635    /// Subnets: NNS, SNS.
636    pub sns: Option<IcpFeaturesConfig>,
637    /// Deploys the Internet Identity canister.
638    /// Subnets: II.
639    pub ii: Option<IcpFeaturesConfig>,
640    /// Deploys the NNS frontend dapp. The HTTP gateway must be specified via `http_gateway_config` in `InstanceConfig`
641    /// and the ICP features `cycles_minting`, `icp_token`, `nns_governance`, `sns`, `ii` must all be enabled.
642    /// Subnets: NNS.
643    pub nns_ui: Option<IcpFeaturesConfig>,
644    /// Deploys the Bitcoin canister under the testnet canister ID `g4xu7-jiaaa-aaaan-aaaaq-cai` and configured for the regtest network.
645    /// Subnets: Bitcoin.
646    pub bitcoin: Option<IcpFeaturesConfig>,
647    /// Deploys the Dogecoin canister under the mainnet canister ID `gordg-fyaaa-aaaan-aaadq-cai` and configured for the regtest network.
648    /// Subnets: Bitcoin.
649    pub dogecoin: Option<IcpFeaturesConfig>,
650    /// Deploys the canister migration orchestrator canister.
651    /// Subnets: NNS.
652    pub canister_migration: Option<IcpFeaturesConfig>,
653}
654
655#[derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize, JsonSchema)]
656pub enum InitialTime {
657    /// Sets the initial timestamp of the new instance to the provided value which must be at least
658    /// - 10 May 2021 10:00:01 AM CEST if the `cycles_minting` feature is enabled in `icp_features`;
659    /// - 06 May 2021 21:17:10 CEST otherwise.
660    Timestamp(RawTime),
661    /// Configures the new instance to make progress automatically,
662    /// i.e., periodically update the time of the IC instance
663    /// to the real time and execute rounds on the subnets.
664    /// Creating the instance only returns after the certified time
665    /// of the IC instance has been updated for the first time.
666    AutoProgress(AutoProgressConfig),
667}
668
669#[derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize, Default, JsonSchema)]
670pub enum IncompleteStateFlag {
671    #[default]
672    Disabled,
673    Enabled,
674}
675
676#[derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize, Default, JsonSchema)]
677pub struct InstanceConfig {
678    pub subnet_config_set: ExtendedSubnetConfigSet,
679    pub http_gateway_config: Option<InstanceHttpGatewayConfig>,
680    pub state_dir: Option<PathBuf>,
681    pub icp_config: Option<IcpConfig>,
682    pub log_level: Option<String>,
683    pub bitcoind_addr: Option<Vec<SocketAddr>>,
684    pub dogecoind_addr: Option<Vec<SocketAddr>>,
685    pub icp_features: Option<IcpFeatures>,
686    pub incomplete_state: Option<IncompleteStateFlag>,
687    pub initial_time: Option<InitialTime>,
688    pub mainnet_nns_subnet_id: Option<bool>,
689    pub disable_ingress_validation: Option<bool>,
690}
691
692#[derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize, Default, JsonSchema)]
693pub struct ExtendedSubnetConfigSet {
694    pub nns: Option<SubnetSpec>,
695    pub sns: Option<SubnetSpec>,
696    pub ii: Option<SubnetSpec>,
697    pub fiduciary: Option<SubnetSpec>,
698    pub bitcoin: Option<SubnetSpec>,
699    pub test_threshold_keys: Option<SubnetSpec>,
700    pub system: Vec<SubnetSpec>,
701    pub application: Vec<SubnetSpec>,
702    #[serde(default)]
703    pub cloud_engine: Vec<SubnetSpec>,
704    pub verified_application: Vec<SubnetSpec>,
705}
706
707/// Specifies various configurations for a subnet.
708#[derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize, JsonSchema)]
709pub struct SubnetSpec {
710    state_config: SubnetStateConfig,
711    instruction_config: SubnetInstructionConfig,
712    subnet_admins: Option<Vec<RawPrincipalId>>,
713    #[serde(default)]
714    cost_schedule: CanisterCyclesCostSchedule,
715}
716
717impl SubnetSpec {
718    pub fn with_state_dir(mut self, path: PathBuf) -> SubnetSpec {
719        self.state_config = SubnetStateConfig::FromPath(path);
720        self
721    }
722
723    pub fn with_benchmarking_instruction_config(mut self) -> SubnetSpec {
724        self.instruction_config = SubnetInstructionConfig::Benchmarking;
725        self
726    }
727
728    pub fn get_state_path(&self) -> Option<PathBuf> {
729        self.state_config.get_path()
730    }
731
732    pub fn get_instruction_config(&self) -> SubnetInstructionConfig {
733        self.instruction_config.clone()
734    }
735
736    pub fn is_supported(&self) -> bool {
737        match &self.state_config {
738            SubnetStateConfig::New => true,
739            SubnetStateConfig::FromPath(..) => true,
740            SubnetStateConfig::FromBlobStore(..) => false,
741        }
742    }
743
744    pub fn with_subnet_admins(mut self, subnet_admins: Vec<Principal>) -> SubnetSpec {
745        self.subnet_admins = Some(
746            subnet_admins
747                .into_iter()
748                .map(RawPrincipalId::from)
749                .collect(),
750        );
751        self
752    }
753
754    pub fn with_cost_schedule(mut self, cost_schedule: CanisterCyclesCostSchedule) -> SubnetSpec {
755        self.cost_schedule = cost_schedule;
756        self
757    }
758
759    pub fn get_subnet_admins(&self) -> Option<Vec<Principal>> {
760        self.subnet_admins
761            .clone()
762            .map(|subnet_admins| subnet_admins.into_iter().map(Principal::from).collect())
763    }
764
765    pub fn get_cost_schedule(&self) -> CanisterCyclesCostSchedule {
766        self.cost_schedule
767    }
768}
769
770impl Default for SubnetSpec {
771    fn default() -> Self {
772        Self {
773            state_config: SubnetStateConfig::New,
774            instruction_config: SubnetInstructionConfig::Production,
775            subnet_admins: None,
776            cost_schedule: Default::default(),
777        }
778    }
779}
780
781/// Specifies instruction limits for canister execution on this subnet.
782#[derive(
783    Debug, Clone, Eq, Hash, PartialEq, Ord, PartialOrd, Serialize, Deserialize, JsonSchema,
784)]
785pub enum SubnetInstructionConfig {
786    /// Use default instruction limits as in production.
787    Production,
788    /// Use very high instruction limits useful for asymptotic canister benchmarking.
789    Benchmarking,
790}
791
792/// Specifies whether the subnet should be created from scratch or loaded
793/// from a path.
794#[derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize, JsonSchema)]
795pub enum SubnetStateConfig {
796    /// Create new subnet with empty state.
797    New,
798    /// Load existing subnet state from the given path.
799    /// The path must be on a filesystem accessible to the server process.
800    FromPath(PathBuf),
801    /// Load existing subnet state from blobstore. Needs to be uploaded first!
802    /// Not implemented!
803    FromBlobStore(BlobId),
804}
805
806impl SubnetStateConfig {
807    pub fn get_path(&self) -> Option<PathBuf> {
808        match self {
809            SubnetStateConfig::FromPath(path) => Some(path.clone()),
810            SubnetStateConfig::FromBlobStore(_) => None,
811            SubnetStateConfig::New => None,
812        }
813    }
814}
815
816impl ExtendedSubnetConfigSet {
817    fn named_subnet_spec(&self, named: NamedSubnet) -> Option<SubnetSpec> {
818        match named {
819            NamedSubnet::NNS => self.nns.clone(),
820            NamedSubnet::SNS => self.sns.clone(),
821            NamedSubnet::II => self.ii.clone(),
822            NamedSubnet::Fiduciary => self.fiduciary.clone(),
823            NamedSubnet::Bitcoin => self.bitcoin.clone(),
824            NamedSubnet::TestThresholdKeys => self.test_threshold_keys.clone(),
825        }
826    }
827
828    // Return the configured named subnets in order.
829    pub fn get_named(&self) -> Vec<(SubnetKind, Option<PathBuf>, SubnetInstructionConfig)> {
830        NamedSubnet::iter()
831            .filter_map(|named| {
832                self.named_subnet_spec(named).map(|spec| {
833                    (
834                        SubnetKind::from(named),
835                        spec.get_state_path(),
836                        spec.get_instruction_config(),
837                    )
838                })
839            })
840            .collect()
841    }
842
843    pub fn validate(&self) -> Result<(), String> {
844        let all_but_application_and_cloud_engine = self
845            .nns
846            .iter()
847            .chain(&self.sns)
848            .chain(&self.ii)
849            .chain(&self.fiduciary)
850            .chain(&self.bitcoin)
851            .chain(&self.test_threshold_keys)
852            .chain(&self.system)
853            .chain(&self.verified_application);
854
855        let all = all_but_application_and_cloud_engine
856            .clone()
857            .chain(&self.application)
858            .chain(&self.cloud_engine);
859
860        // 1. Check for invalid admins using a clone of the iterator (to prevent its consumption).
861        if all_but_application_and_cloud_engine
862            .clone()
863            .any(|spec| spec.subnet_admins.is_some())
864        {
865            return Err(
866                "Subnet admins can only be specified for subnet of kind `Application` or `CloudEngine`".into(),
867            );
868        }
869
870        // 2. Check for invalid cost schedules using a clone of the iterator (to prevent its consumption).
871        if all_but_application_and_cloud_engine
872            .clone()
873            .any(|spec| spec.cost_schedule != Default::default())
874        {
875            return Err("Non-default cost schedule can only be specified for subnet of kind `Application` or `CloudEngine`".into());
876        }
877
878        // 3. Check for invalid combinations of subnet admins and cost schedule using a clone of the iterator (to prevent its consumption).
879        if all.clone().any(|spec| {
880            spec.subnet_admins.is_some() && spec.cost_schedule != CanisterCyclesCostSchedule::Free
881        }) {
882            return Err(
883                "Subnet admins can only be specified for subnet with cost schedule of kind `Free`"
884                    .into(),
885            );
886        }
887
888        // 4. Cloud engines must have a "free" cost schedule.
889        if self
890            .cloud_engine
891            .iter()
892            .any(|spec| spec.cost_schedule != CanisterCyclesCostSchedule::Free)
893        {
894            return Err(
895                "Every subnet of kind `CloudEngine` must have cost schedule of kind `Free`".into(),
896            );
897        }
898
899        // 5. Check for existence across everything (again cloning the iterator to prevent its consumption).
900        let has_any = all.clone().next().is_some();
901
902        if !has_any {
903            return Err("ExtendedSubnetConfigSet must contain at least one subnet".into());
904        }
905
906        Ok(())
907    }
908
909    pub fn try_with_icp_features(mut self, icp_features: &IcpFeatures) -> Result<Self, String> {
910        let check_empty_subnet = |subnet: &Option<SubnetSpec>, subnet_desc, icp_feature| {
911            if let Some(config) = subnet
912                && !matches!(config.state_config, SubnetStateConfig::New)
913            {
914                return Err(format!(
915                    "The {subnet_desc} subnet must be empty when specifying the `{icp_feature}` ICP feature."
916                ));
917            }
918            Ok(())
919        };
920        // using `let IcpFeatures { }` with explicit field names
921        // to force an update after adding a new field to `IcpFeatures`
922        let IcpFeatures {
923            registry,
924            cycles_minting,
925            icp_token,
926            cycles_token,
927            nns_governance,
928            sns,
929            ii,
930            nns_ui,
931            bitcoin,
932            dogecoin,
933            canister_migration,
934        } = icp_features;
935        // NNS canisters
936        for (flag, icp_feature_str) in [
937            (registry, "registry"),
938            (cycles_minting, "cycles_minting"),
939            (icp_token, "icp_token"),
940            (nns_governance, "nns_governance"),
941            (sns, "sns"),
942            (nns_ui, "nns_ui"),
943            (canister_migration, "canister_migration"),
944        ] {
945            if flag.is_some() {
946                check_empty_subnet(&self.nns, "NNS", icp_feature_str)?;
947                self.nns = Some(self.nns.unwrap_or_default());
948            }
949        }
950        // canisters on the II subnet
951        for (flag, icp_feature_str) in [(cycles_token, "cycles_token"), (ii, "ii")] {
952            if flag.is_some() {
953                check_empty_subnet(&self.ii, "II", icp_feature_str)?;
954                self.ii = Some(self.ii.unwrap_or_default());
955            }
956        }
957        // canisters on the SNS subnet
958        if sns.is_some() {
959            check_empty_subnet(&self.sns, "SNS", "sns")?;
960            self.sns = Some(self.sns.unwrap_or_default());
961        }
962        // canisters on the Bitcoin subnet
963        for (flag, icp_feature_str) in [(bitcoin, "bitcoin"), (dogecoin, "dogecoin")] {
964            if flag.is_some() {
965                check_empty_subnet(&self.bitcoin, "Bitcoin", icp_feature_str)?;
966                self.bitcoin = Some(self.bitcoin.unwrap_or_default());
967            }
968        }
969        Ok(self)
970    }
971}
972
973/// Specifies how to charge canisters for their use of computational resources (such as
974/// executing instructions, storing data, network, etc.).
975#[derive(
976    Debug,
977    Default,
978    Hash,
979    Clone,
980    Copy,
981    Eq,
982    PartialEq,
983    Ord,
984    PartialOrd,
985    Serialize,
986    Deserialize,
987    JsonSchema,
988)]
989pub enum CanisterCyclesCostSchedule {
990    #[default]
991    Normal,
992    Free,
993}
994
995/// Configuration details for a subnet, returned by PocketIc server
996#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize, JsonSchema)]
997pub struct SubnetConfig {
998    pub subnet_kind: SubnetKind,
999    pub subnet_admins: Option<Vec<RawPrincipalId>>,
1000    pub cost_schedule: CanisterCyclesCostSchedule,
1001    pub subnet_seed: [u8; 32],
1002    /// Instruction limits for canister execution on this subnet.
1003    pub instruction_config: SubnetInstructionConfig,
1004    /// Some mainnet subnets have several disjunct canister ranges.
1005    pub canister_ranges: Vec<CanisterIdRange>,
1006}
1007
1008#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize, JsonSchema)]
1009pub struct CanisterIdRange {
1010    pub start: RawCanisterId,
1011    pub end: RawCanisterId,
1012}
1013
1014impl CanisterIdRange {
1015    fn contains(&self, canister_id: Principal) -> bool {
1016        Principal::from_slice(&self.start.canister_id) <= canister_id
1017            && canister_id <= Principal::from_slice(&self.end.canister_id)
1018    }
1019}
1020
1021#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize, JsonSchema)]
1022pub struct Topology {
1023    pub subnet_configs: BTreeMap<SubnetId, SubnetConfig>,
1024    pub default_effective_canister_id: RawCanisterId,
1025}
1026
1027impl Topology {
1028    pub fn get_subnet(&self, canister_id: Principal) -> Option<SubnetId> {
1029        self.subnet_configs
1030            .iter()
1031            .find(|(_, config)| {
1032                config
1033                    .canister_ranges
1034                    .iter()
1035                    .any(|r| r.contains(canister_id))
1036            })
1037            .map(|(subnet_id, _)| subnet_id)
1038            .copied()
1039    }
1040
1041    pub fn get_app_subnets(&self) -> Vec<SubnetId> {
1042        self.find_subnets(SubnetKind::Application, None)
1043    }
1044
1045    pub fn get_cloud_engines(&self) -> Vec<SubnetId> {
1046        self.find_subnets(SubnetKind::CloudEngine, None)
1047    }
1048
1049    pub fn get_verified_app_subnets(&self) -> Vec<SubnetId> {
1050        self.find_subnets(SubnetKind::VerifiedApplication, None)
1051    }
1052
1053    pub fn get_benchmarking_app_subnets(&self) -> Vec<SubnetId> {
1054        self.find_subnets(
1055            SubnetKind::Application,
1056            Some(SubnetInstructionConfig::Benchmarking),
1057        )
1058    }
1059
1060    pub fn get_bitcoin(&self) -> Option<SubnetId> {
1061        self.find_subnet(SubnetKind::Bitcoin)
1062    }
1063
1064    pub fn get_fiduciary(&self) -> Option<SubnetId> {
1065        self.find_subnet(SubnetKind::Fiduciary)
1066    }
1067
1068    pub fn get_ii(&self) -> Option<SubnetId> {
1069        self.find_subnet(SubnetKind::II)
1070    }
1071
1072    pub fn get_nns(&self) -> Option<SubnetId> {
1073        self.find_subnet(SubnetKind::NNS)
1074    }
1075
1076    pub fn get_sns(&self) -> Option<SubnetId> {
1077        self.find_subnet(SubnetKind::SNS)
1078    }
1079
1080    pub fn get_system_subnets(&self) -> Vec<SubnetId> {
1081        self.find_subnets(SubnetKind::System, None)
1082    }
1083
1084    fn find_subnets(
1085        &self,
1086        kind: SubnetKind,
1087        instruction_config: Option<SubnetInstructionConfig>,
1088    ) -> Vec<SubnetId> {
1089        self.subnet_configs
1090            .iter()
1091            .filter(|(_, config)| {
1092                config.subnet_kind == kind
1093                    && instruction_config
1094                        .as_ref()
1095                        .map(|instruction_config| config.instruction_config == *instruction_config)
1096                        .unwrap_or(true)
1097            })
1098            .map(|(id, _)| *id)
1099            .collect()
1100    }
1101
1102    fn find_subnet(&self, kind: SubnetKind) -> Option<SubnetId> {
1103        self.subnet_configs
1104            .iter()
1105            .find(|(_, config)| config.subnet_kind == kind)
1106            .map(|(id, _)| *id)
1107    }
1108}
1109
1110#[derive(
1111    Clone, Serialize, Deserialize, Debug, Hash, Eq, PartialEq, Ord, PartialOrd, JsonSchema,
1112)]
1113pub enum CanisterHttpMethod {
1114    GET,
1115    POST,
1116    HEAD,
1117    PUT,
1118    DELETE,
1119    PATCH,
1120}
1121
1122#[derive(
1123    Clone, Serialize, Deserialize, Debug, Hash, Eq, PartialEq, Ord, PartialOrd, JsonSchema,
1124)]
1125pub struct CanisterHttpHeader {
1126    pub name: String,
1127    pub value: String,
1128}
1129
1130/// How a canister HTTP outcall is replicated across the nodes of its subnet.
1131#[derive(
1132    Clone, Serialize, Deserialize, Debug, Hash, Eq, PartialEq, Ord, PartialOrd, JsonSchema,
1133)]
1134pub enum CanisterHttpReplication {
1135    /// Every node of the subnet performs the outcall and a response is delivered
1136    /// once `n - f` of them agree on it. Too many differing responses make the
1137    /// outcall fail with a "no consensus could be reached" rejection instead.
1138    /// Mock such an outcall with `PocketIc::mock_canister_http_response`, whose
1139    /// `additional_responses` are what lets a test produce either outcome.
1140    FullyReplicated,
1141    /// One node of the subnet performs the outcall (`is_replicated = false`) and
1142    /// its response is the one delivered. PocketIC does not report which node was
1143    /// picked, so mock such an outcall with
1144    /// `PocketIc::mock_canister_http_response` like a fully replicated one: give it
1145    /// a single `response`, which every node then reports, and it is delivered no
1146    /// matter which node was picked. Differing `additional_responses` would instead
1147    /// make the delivered response unpredictable.
1148    NonReplicated,
1149    /// A committee of `total_requests` nodes performs the outcall and between
1150    /// `min_responses` and `max_responses` of their (potentially differing)
1151    /// responses are delivered to the calling canister. Mock such an outcall
1152    /// with `PocketIc::mock_flexible_canister_http_response`.
1153    Flexible {
1154        /// The number of nodes performing the outcall.
1155        total_requests: u32,
1156        /// The number of responses required to deliver a result.
1157        min_responses: u32,
1158        /// The largest number of responses that may be delivered.
1159        max_responses: u32,
1160    },
1161}
1162
1163/// The pricing model applied to a canister HTTP outcall.
1164#[derive(
1165    Clone, Serialize, Deserialize, Debug, Hash, Eq, PartialEq, Ord, PartialOrd, JsonSchema,
1166)]
1167pub enum CanisterHttpPricingVersion {
1168    /// The whole cost of the outcall is charged up front, based on the largest
1169    /// response it could receive.
1170    Legacy,
1171    /// The outcall is charged for the resources it actually consumes: a base fee is
1172    /// charged up front, a per-replica cycles allowance is withheld from the
1173    /// payment, and whatever the responding nodes do not spend is refunded.
1174    PayAsYouGo,
1175}
1176
1177#[derive(Clone, Serialize, Deserialize, Debug, JsonSchema)]
1178pub struct RawCanisterHttpRequest {
1179    pub subnet_id: RawSubnetId,
1180    pub request_id: u64,
1181    pub http_method: CanisterHttpMethod,
1182    pub url: String,
1183    pub headers: Vec<CanisterHttpHeader>,
1184    #[serde(deserialize_with = "base64::deserialize")]
1185    #[serde(serialize_with = "base64::serialize")]
1186    pub body: Vec<u8>,
1187    pub max_response_bytes: Option<u64>,
1188    pub replication: CanisterHttpReplication,
1189    pub pricing_version: CanisterHttpPricingVersion,
1190}
1191
1192#[derive(Clone, Serialize, Deserialize, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)]
1193pub struct CanisterHttpRequest {
1194    pub subnet_id: Principal,
1195    pub request_id: u64,
1196    pub http_method: CanisterHttpMethod,
1197    pub url: String,
1198    pub headers: Vec<CanisterHttpHeader>,
1199    #[serde(deserialize_with = "base64::deserialize")]
1200    #[serde(serialize_with = "base64::serialize")]
1201    pub body: Vec<u8>,
1202    pub max_response_bytes: Option<u64>,
1203    pub replication: CanisterHttpReplication,
1204    pub pricing_version: CanisterHttpPricingVersion,
1205}
1206
1207impl From<RawCanisterHttpRequest> for CanisterHttpRequest {
1208    fn from(raw_canister_http_request: RawCanisterHttpRequest) -> Self {
1209        Self {
1210            subnet_id: candid::Principal::from_slice(
1211                &raw_canister_http_request.subnet_id.subnet_id,
1212            ),
1213            request_id: raw_canister_http_request.request_id,
1214            http_method: raw_canister_http_request.http_method,
1215            url: raw_canister_http_request.url,
1216            headers: raw_canister_http_request.headers,
1217            body: raw_canister_http_request.body,
1218            max_response_bytes: raw_canister_http_request.max_response_bytes,
1219            replication: raw_canister_http_request.replication,
1220            pricing_version: raw_canister_http_request.pricing_version,
1221        }
1222    }
1223}
1224
1225impl From<CanisterHttpRequest> for RawCanisterHttpRequest {
1226    fn from(canister_http_request: CanisterHttpRequest) -> Self {
1227        Self {
1228            subnet_id: canister_http_request.subnet_id.into(),
1229            request_id: canister_http_request.request_id,
1230            http_method: canister_http_request.http_method,
1231            url: canister_http_request.url,
1232            headers: canister_http_request.headers,
1233            body: canister_http_request.body,
1234            max_response_bytes: canister_http_request.max_response_bytes,
1235            replication: canister_http_request.replication,
1236            pricing_version: canister_http_request.pricing_version,
1237        }
1238    }
1239}
1240
1241#[derive(
1242    Clone, Serialize, Deserialize, Debug, Hash, Eq, PartialEq, Ord, PartialOrd, JsonSchema,
1243)]
1244pub struct CanisterHttpReply {
1245    pub status: u16,
1246    pub headers: Vec<CanisterHttpHeader>,
1247    #[serde(deserialize_with = "base64::deserialize")]
1248    #[serde(serialize_with = "base64::serialize")]
1249    pub body: Vec<u8>,
1250}
1251
1252#[derive(
1253    Clone, Serialize, Deserialize, Debug, Hash, Eq, PartialEq, Ord, PartialOrd, JsonSchema,
1254)]
1255pub struct CanisterHttpReject {
1256    pub reject_code: u64,
1257    /// Bounded by the 1 KiB a node truncates its reject messages to.
1258    pub message: String,
1259}
1260
1261#[derive(
1262    Clone, Serialize, Deserialize, Debug, Hash, Eq, PartialEq, Ord, PartialOrd, JsonSchema,
1263)]
1264pub enum CanisterHttpResponse {
1265    CanisterHttpReply(CanisterHttpReply),
1266    CanisterHttpReject(CanisterHttpReject),
1267}
1268
1269#[derive(Clone, Serialize, Deserialize, Debug, JsonSchema)]
1270pub struct RawMockCanisterHttpResponse {
1271    pub subnet_id: RawSubnetId,
1272    pub request_id: u64,
1273    pub response: CanisterHttpResponse,
1274    pub additional_responses: Vec<CanisterHttpResponse>,
1275}
1276
1277/// Mocked responses to a pending canister HTTP outcall made through the
1278/// `http_request` management canister endpoint — whether fully replicated or
1279/// non-replicated (see [`CanisterHttpReplication`]). A *flexible* outcall is
1280/// answered with [`MockFlexibleCanisterHttpResponse`] instead.
1281///
1282/// This answers for every node of the subnet, which is what an `http_request`
1283/// outcall needs: a fully replicated one is performed by all of them, and a
1284/// non-replicated one by a single node that is not exposed. It is therefore also
1285/// the only shape that can make the nodes of a fully replicated outcall disagree
1286/// (see `additional_responses`).
1287///
1288/// An `http_request` outcall answered this way reports one of:
1289/// - the mocked response — as a reply, or as a rejection of the
1290///   calling canister's call carrying the mocked code and message;
1291/// - `SysTransient` and "No consensus could be reached" if its nodes disagree (see
1292///   `additional_responses`; a non-replicated outcall has no such outcome, it just
1293///   delivers its designated node's response);
1294/// - `SysTransient` and "Out of cycles" if what the nodes left unspent of their
1295///   per-replica cycles allowances no longer covers putting a response into a
1296///   block;
1297/// - `SysTransient` and "Canister http request timed out" once the outcall is
1298///   older than its 60 second timeout.
1299#[derive(Clone, Serialize, Deserialize, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)]
1300pub struct MockCanisterHttpResponse {
1301    pub subnet_id: Principal,
1302    pub request_id: u64,
1303    /// The response every node of the subnet reports, unless
1304    /// `additional_responses` is non-empty — in which case this is the response of
1305    /// one node and `additional_responses` holds the rest.
1306    ///
1307    /// With this response alone, all nodes agree, so a fully replicated outcall
1308    /// delivers it (see [`CanisterHttpReplication::FullyReplicated`]).
1309    pub response: CanisterHttpResponse,
1310    /// The responses of the remaining nodes of the subnet. Either empty — then
1311    /// `response` is used for every node — or exactly one short of the subnet size,
1312    /// so that together with `response` there is one response per node.
1313    ///
1314    /// This is how a test makes the nodes of a *fully replicated* outcall disagree:
1315    /// unless `n - f` of the `n` responses are equal, it fails with a "no consensus
1316    /// could be reached" rejection.
1317    ///
1318    /// A *non-replicated* outcall has nothing to disagree about — only its
1319    /// designated node's response is delivered, and PocketIC does not report which
1320    /// node that is — so differing responses merely make the delivered one
1321    /// unpredictable. Leave this empty unless the disagreement is what is under
1322    /// test.
1323    pub additional_responses: Vec<CanisterHttpResponse>,
1324}
1325
1326impl From<RawMockCanisterHttpResponse> for MockCanisterHttpResponse {
1327    fn from(raw_mock_canister_http_response: RawMockCanisterHttpResponse) -> Self {
1328        Self {
1329            subnet_id: candid::Principal::from_slice(
1330                &raw_mock_canister_http_response.subnet_id.subnet_id,
1331            ),
1332            request_id: raw_mock_canister_http_response.request_id,
1333            response: raw_mock_canister_http_response.response,
1334            additional_responses: raw_mock_canister_http_response.additional_responses,
1335        }
1336    }
1337}
1338
1339impl From<MockCanisterHttpResponse> for RawMockCanisterHttpResponse {
1340    fn from(mock_canister_http_response: MockCanisterHttpResponse) -> Self {
1341        Self {
1342            subnet_id: RawSubnetId {
1343                subnet_id: mock_canister_http_response.subnet_id.as_slice().to_vec(),
1344            },
1345            request_id: mock_canister_http_response.request_id,
1346            response: mock_canister_http_response.response,
1347            additional_responses: mock_canister_http_response.additional_responses,
1348        }
1349    }
1350}
1351
1352#[derive(Clone, Serialize, Deserialize, Debug, JsonSchema)]
1353pub struct RawMockFlexibleCanisterHttpResponse {
1354    pub subnet_id: RawSubnetId,
1355    pub request_id: u64,
1356    pub responses: Vec<CanisterHttpResponse>,
1357}
1358
1359/// Mocked responses to a pending *flexible* canister HTTP outcall, i.e. one made
1360/// through the `flexible_http_request` management canister endpoint.
1361///
1362/// A flexible outcall is performed by a committee of `total_requests` nodes whose
1363/// differing responses are all legitimate, several of which are delivered, and which
1364/// need not all arrive (see [`CanisterHttpReplication::Flexible`]). Leaving some of them
1365/// silent is what models a slow or unresponsive committee.
1366#[derive(Clone, Serialize, Deserialize, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)]
1367pub struct MockFlexibleCanisterHttpResponse {
1368    pub subnet_id: Principal,
1369    pub request_id: u64,
1370    /// One response per committee node that responded. Each response is attributed
1371    /// to a different committee node.
1372    ///
1373    /// There may be at most `total_requests` responses; providing fewer models a
1374    /// committee whose remaining nodes never respond. What the outcall reports is
1375    /// then decided in this order:
1376    /// - once the outcall is older than its 60 second timeout it reports a
1377    ///   `timeout`;
1378    /// - at least `min_responses` replies deliver a result — unless they cannot be
1379    ///   delivered, in which case one of the outcomes below applies instead;
1380    /// - more than `total_requests - min_responses` rejects — i.e. so
1381    ///   many that `min_responses` replies can no longer be reached — deliver a
1382    ///   `too_many_rejects` error naming the rejecting nodes;
1383    /// - if the smallest replies that would have to be delivered together do not
1384    ///   fit into the 2 MiB a block has for HTTP outcall responses, a
1385    ///   `responses_too_large` error (each response may still be within the 2 MB
1386    ///   cap on a single one);
1387    /// - if what the nodes left unspent of their per-replica cycles allowances no
1388    ///   longer covers putting a response into a block, an `out_of_cycles`;
1389    /// - anything else leaves the outcall pending, so that advancing the time past
1390    ///   its timeout delivers a `timeout` error.
1391    ///
1392    /// All of these are replies to the calling canister, not rejections of its
1393    /// call.
1394    pub responses: Vec<CanisterHttpResponse>,
1395}
1396
1397impl From<RawMockFlexibleCanisterHttpResponse> for MockFlexibleCanisterHttpResponse {
1398    fn from(raw_mock_flexible_canister_http_response: RawMockFlexibleCanisterHttpResponse) -> Self {
1399        Self {
1400            subnet_id: candid::Principal::from_slice(
1401                &raw_mock_flexible_canister_http_response.subnet_id.subnet_id,
1402            ),
1403            request_id: raw_mock_flexible_canister_http_response.request_id,
1404            responses: raw_mock_flexible_canister_http_response.responses,
1405        }
1406    }
1407}
1408
1409impl From<MockFlexibleCanisterHttpResponse> for RawMockFlexibleCanisterHttpResponse {
1410    fn from(mock_flexible_canister_http_response: MockFlexibleCanisterHttpResponse) -> Self {
1411        Self {
1412            subnet_id: RawSubnetId {
1413                subnet_id: mock_flexible_canister_http_response
1414                    .subnet_id
1415                    .as_slice()
1416                    .to_vec(),
1417            },
1418            request_id: mock_flexible_canister_http_response.request_id,
1419            responses: mock_flexible_canister_http_response.responses,
1420        }
1421    }
1422}
1423
1424#[derive(Clone, Serialize, Deserialize, Debug, JsonSchema)]
1425pub struct RawCanisterSnapshotDownload {
1426    pub sender: RawPrincipalId,
1427    pub canister_id: RawCanisterId,
1428    #[serde(deserialize_with = "base64::deserialize")]
1429    #[serde(serialize_with = "base64::serialize")]
1430    pub snapshot_id: Vec<u8>,
1431    pub snapshot_dir: PathBuf,
1432}
1433
1434#[derive(Clone, Serialize, Deserialize, Debug, JsonSchema)]
1435pub struct RawCanisterSnapshotUpload {
1436    pub sender: RawPrincipalId,
1437    pub canister_id: RawCanisterId,
1438    pub replace_snapshot: Option<RawCanisterSnapshotId>,
1439    pub snapshot_dir: PathBuf,
1440}
1441
1442#[derive(Clone, Serialize, Deserialize, Debug, JsonSchema)]
1443pub struct RawCanisterSnapshotId {
1444    #[serde(deserialize_with = "base64::deserialize")]
1445    #[serde(serialize_with = "base64::serialize")]
1446    pub snapshot_id: Vec<u8>,
1447}