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//! This module retains the protocol DTOs used by the maintained client.
4//! The server owns configuration validation and system-canister provisioning.
5
6use crate::pocket_ic::RejectResponse;
7use candid::Principal;
8use hex;
9use serde::{Deserialize, Serialize};
10use std::collections::BTreeMap;
11use std::path::PathBuf;
12
13pub type InstanceId = usize;
14
15#[derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize)]
16pub struct AutoProgressConfig {
17    pub artificial_delay_ms: Option<u64>,
18}
19
20#[derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize)]
21pub enum HttpGatewayBackend {
22    Replica(String),
23    PocketIcInstance(InstanceId),
24}
25
26#[derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize)]
27pub struct HttpsConfig {
28    pub cert_path: String,
29    pub key_path: String,
30}
31
32#[derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize)]
33pub struct InstanceHttpGatewayConfig {
34    pub ip_addr: Option<String>,
35    pub port: Option<u16>,
36    pub domains: Option<Vec<String>>,
37    pub https_config: Option<HttpsConfig>,
38    pub domain_custom_provider_local_file: Option<String>,
39}
40
41#[derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize)]
42pub struct HttpGatewayConfig {
43    pub ip_addr: Option<String>,
44    pub port: Option<u16>,
45    pub forward_to: HttpGatewayBackend,
46    pub domains: Option<Vec<String>>,
47    pub https_config: Option<HttpsConfig>,
48    pub domain_custom_provider_local_file: Option<String>,
49}
50
51#[derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize)]
52pub struct HttpGatewayInfo {
53    pub instance_id: InstanceId,
54    pub port: u16,
55}
56
57#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
58pub enum CreateHttpGatewayResponse {
59    Created(HttpGatewayInfo),
60    Error { message: String },
61}
62
63#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
64pub enum CreateInstanceResponse {
65    Created {
66        instance_id: InstanceId,
67        topology: Topology,
68        http_gateway_info: Option<HttpGatewayInfo>,
69    },
70    Error {
71        message: String,
72    },
73}
74
75#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq, Serialize, Deserialize)]
76pub struct RawTime {
77    pub nanos_since_epoch: u64,
78}
79
80/// Relevant for calls to the management canister. If a subnet ID is
81/// provided, the call will be sent to the management canister of that subnet.
82/// If a canister ID is provided, the call will be sent to the management
83/// canister of the subnet where the canister is on.
84/// If None, the call will be sent to any management canister.
85#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Hash)]
86pub enum RawEffectivePrincipal {
87    None,
88    SubnetId(
89        #[serde(deserialize_with = "base64::deserialize")]
90        #[serde(serialize_with = "base64::serialize")]
91        Vec<u8>,
92    ),
93    CanisterId(
94        #[serde(deserialize_with = "base64::deserialize")]
95        #[serde(serialize_with = "base64::serialize")]
96        Vec<u8>,
97    ),
98}
99
100impl std::fmt::Display for RawEffectivePrincipal {
101    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
102        match self {
103            RawEffectivePrincipal::None => write!(f, "None"),
104            RawEffectivePrincipal::SubnetId(subnet_id) => {
105                let principal = Principal::from_slice(subnet_id);
106                write!(f, "SubnetId({principal})")
107            }
108            RawEffectivePrincipal::CanisterId(canister_id) => {
109                let principal = Principal::from_slice(canister_id);
110                write!(f, "CanisterId({principal})")
111            }
112        }
113    }
114}
115
116#[derive(Clone, Serialize, Deserialize, Debug)]
117pub struct RawMessageId {
118    pub effective_principal: RawEffectivePrincipal,
119    #[serde(deserialize_with = "base64::deserialize")]
120    #[serde(serialize_with = "base64::serialize")]
121    pub message_id: Vec<u8>,
122}
123
124#[derive(Clone, Serialize, Deserialize, Debug)]
125pub struct RawIngressStatusArgs {
126    pub raw_message_id: RawMessageId,
127    pub raw_caller: Option<RawPrincipalId>,
128}
129
130#[derive(Clone, Serialize, Deserialize, Debug)]
131pub struct RawSenderInfo {
132    #[serde(deserialize_with = "base64::deserialize")]
133    #[serde(serialize_with = "base64::serialize")]
134    pub info: Vec<u8>,
135    #[serde(deserialize_with = "base64::deserialize")]
136    #[serde(serialize_with = "base64::serialize")]
137    pub signer: Vec<u8>,
138}
139
140#[derive(Clone, Serialize, Deserialize, Debug)]
141pub struct RawCanisterCall {
142    #[serde(deserialize_with = "base64::deserialize")]
143    #[serde(serialize_with = "base64::serialize")]
144    pub sender: Vec<u8>,
145    #[serde(deserialize_with = "base64::deserialize")]
146    #[serde(serialize_with = "base64::serialize")]
147    pub canister_id: Vec<u8>,
148    pub effective_principal: RawEffectivePrincipal,
149    pub method: String,
150    #[serde(deserialize_with = "base64::deserialize")]
151    #[serde(serialize_with = "base64::serialize")]
152    pub payload: Vec<u8>,
153    #[serde(skip_serializing_if = "Option::is_none")]
154    pub sender_info: Option<RawSenderInfo>,
155}
156
157#[derive(Clone, Serialize, Deserialize, Debug)]
158pub enum RawCanisterResult {
159    Ok(
160        #[serde(deserialize_with = "base64::deserialize")]
161        #[serde(serialize_with = "base64::serialize")]
162        Vec<u8>,
163    ),
164    Err(RejectResponse),
165}
166
167impl From<Result<Vec<u8>, RejectResponse>> for RawCanisterResult {
168    fn from(result: Result<Vec<u8>, RejectResponse>) -> Self {
169        match result {
170            Ok(data) => RawCanisterResult::Ok(data),
171            Err(reject_response) => RawCanisterResult::Err(reject_response),
172        }
173    }
174}
175
176impl From<RawCanisterResult> for Result<Vec<u8>, RejectResponse> {
177    fn from(result: RawCanisterResult) -> Self {
178        match result {
179            RawCanisterResult::Ok(data) => Ok(data),
180            RawCanisterResult::Err(reject_response) => Err(reject_response),
181        }
182    }
183}
184
185#[derive(Clone, Serialize, Deserialize, Debug)]
186pub struct RawSetStableMemory {
187    #[serde(deserialize_with = "base64::deserialize")]
188    #[serde(serialize_with = "base64::serialize")]
189    pub canister_id: Vec<u8>,
190    pub blob_id: BlobId,
191}
192
193#[derive(Clone, Serialize, Deserialize, Debug)]
194pub struct RawStableMemory {
195    #[serde(deserialize_with = "base64::deserialize")]
196    #[serde(serialize_with = "base64::serialize")]
197    pub blob: Vec<u8>,
198}
199
200#[derive(Clone, Serialize, Deserialize, Debug)]
201pub struct ApiError {
202    pub(crate) message: String,
203}
204
205#[derive(Clone, Serialize, Deserialize, Debug)]
206pub struct StartedOrBusyResponse {
207    pub state_label: String,
208    pub op_id: String,
209}
210
211#[derive(Clone, Serialize, Deserialize, Debug)]
212pub struct RawAddCycles {
213    #[serde(deserialize_with = "base64::deserialize")]
214    #[serde(serialize_with = "base64::serialize")]
215    pub canister_id: Vec<u8>,
216    pub amount: u128,
217}
218
219#[derive(Clone, Serialize, Deserialize, Debug)]
220pub struct RawCycles {
221    pub cycles: u128,
222}
223
224#[derive(Clone, Serialize, Hash, Eq, PartialEq, Ord, PartialOrd, Deserialize, Debug)]
225pub struct RawPrincipalId {
226    // raw bytes of the principal
227    #[serde(deserialize_with = "base64::deserialize")]
228    #[serde(serialize_with = "base64::serialize")]
229    pub principal_id: Vec<u8>,
230}
231
232impl From<Principal> for RawPrincipalId {
233    fn from(principal: Principal) -> Self {
234        Self {
235            principal_id: principal.as_slice().to_vec(),
236        }
237    }
238}
239
240impl From<RawPrincipalId> for Principal {
241    fn from(raw_principal_id: RawPrincipalId) -> Self {
242        Principal::from_slice(&raw_principal_id.principal_id)
243    }
244}
245
246#[derive(Clone, Serialize, Eq, PartialEq, Ord, PartialOrd, Deserialize, Debug)]
247pub struct RawCanisterId {
248    // raw bytes of the principal
249    #[serde(deserialize_with = "base64::deserialize")]
250    #[serde(serialize_with = "base64::serialize")]
251    pub canister_id: Vec<u8>,
252}
253
254impl From<Principal> for RawCanisterId {
255    fn from(principal: Principal) -> Self {
256        Self {
257            canister_id: principal.as_slice().to_vec(),
258        }
259    }
260}
261
262impl From<RawCanisterId> for Principal {
263    fn from(raw_canister_id: RawCanisterId) -> Self {
264        Principal::from_slice(&raw_canister_id.canister_id)
265    }
266}
267
268#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Hash)]
269pub struct RawSubnetId {
270    #[serde(deserialize_with = "base64::deserialize")]
271    #[serde(serialize_with = "base64::serialize")]
272    pub subnet_id: Vec<u8>,
273}
274
275pub type SubnetId = Principal;
276
277impl From<Principal> for RawSubnetId {
278    fn from(principal: Principal) -> Self {
279        Self {
280            subnet_id: principal.as_slice().to_vec(),
281        }
282    }
283}
284
285impl From<RawSubnetId> for Principal {
286    fn from(val: RawSubnetId) -> Self {
287        Principal::from_slice(&val.subnet_id)
288    }
289}
290
291#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
292pub struct RawNodeId {
293    #[serde(deserialize_with = "base64::deserialize")]
294    #[serde(serialize_with = "base64::serialize")]
295    pub node_id: Vec<u8>,
296}
297
298impl From<RawNodeId> for Principal {
299    fn from(val: RawNodeId) -> Self {
300        Principal::from_slice(&val.node_id)
301    }
302}
303
304impl From<Principal> for RawNodeId {
305    fn from(principal: Principal) -> Self {
306        Self {
307            node_id: principal.as_slice().to_vec(),
308        }
309    }
310}
311
312#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, Default)]
313pub struct RawTickConfigs {
314    pub blockmakers: Option<Vec<RawSubnetBlockmakers>>,
315}
316
317#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
318pub struct RawSubnetBlockmakers {
319    pub subnet: RawSubnetId,
320    pub blockmaker: RawNodeId,
321    pub failed_blockmakers: Vec<RawNodeId>,
322}
323
324#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
325pub struct BlobId(
326    #[serde(deserialize_with = "base64::deserialize")]
327    #[serde(serialize_with = "base64::serialize")]
328    pub Vec<u8>,
329);
330
331impl std::fmt::Display for BlobId {
332    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
333        write!(f, "BlobId{{{}}}", hex::encode(self.0.clone()))
334    }
335}
336
337#[derive(Clone, Copy, Debug, Eq, PartialEq)]
338pub enum BlobCompression {
339    Gzip,
340    NoCompression,
341}
342
343// By default, serde serializes Vec<u8> to a list of numbers, which is inefficient.
344// This enables serializing Vec<u8> to a compact base64 representation.
345pub mod base64 {
346    use ::base64::prelude::*;
347    use serde::{Deserialize, Serialize};
348    use serde::{Deserializer, Serializer};
349
350    pub fn serialize<S: Serializer>(v: &Vec<u8>, s: S) -> Result<S::Ok, S::Error> {
351        let base64 = BASE64_STANDARD.encode(v);
352        String::serialize(&base64, s)
353    }
354
355    pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<Vec<u8>, D::Error> {
356        let base64 = String::deserialize(d)?;
357        BASE64_STANDARD
358            .decode(base64.as_bytes())
359            .map_err(serde::de::Error::custom)
360    }
361}
362
363// ================================================================================================================= //
364
365#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq, Ord, PartialOrd, Serialize, Deserialize)]
366pub enum SubnetKind {
367    Application,
368    Bitcoin,
369    CloudEngine,
370    Fiduciary,
371    II,
372    NNS,
373    SNS,
374    System,
375    TestThresholdKeys,
376    VerifiedApplication,
377}
378
379#[derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize, Default)]
380pub enum IcpFeaturesConfig {
381    /// Default configuration of an ICP feature resembling mainnet configuration as closely as possible.
382    #[default]
383    DefaultConfig,
384}
385
386/// Specifies ICP features enabled by deploying their corresponding system canisters
387/// when creating a PocketIC instance and keeping them up to date
388/// during the PocketIC instance lifetime.
389/// The PocketIC server admits and provisions the required empty subnets.
390/// An ICP feature is enabled if its `IcpFeaturesConfig` is provided, i.e.,
391/// if the corresponding field is not `None`.
392#[derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize, Default)]
393pub struct IcpFeatures {
394    /// Deploys the NNS registry canister and keeps its content in sync with registry used internally by PocketIC.
395    /// Subnets: NNS.
396    pub registry: Option<IcpFeaturesConfig>,
397    /// Deploys the NNS cycles minting canister, sets ICP/XDR conversion rate, and keeps its subnet lists in sync with PocketIC topology.
398    /// 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).
399    /// Subnets: NNS.
400    pub cycles_minting: Option<IcpFeaturesConfig>,
401    /// Deploys the ICP ledger and index canisters and initializes the ICP account of the anonymous principal with 1,000,000,000 ICP.
402    /// Subnets: NNS.
403    pub icp_token: Option<IcpFeaturesConfig>,
404    /// Deploys the cycles ledger and index canisters and initializes the cycles account of the anonymous principal with 2^127 cycles.
405    /// Subnets: II.
406    pub cycles_token: Option<IcpFeaturesConfig>,
407    /// Deploys the Internet Identity canister.
408    /// Subnets: II.
409    pub ii: Option<IcpFeaturesConfig>,
410}
411
412#[derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize)]
413pub enum InitialTime {
414    /// Sets the initial timestamp of the new instance to the provided value which must be at least
415    /// - 10 May 2021 10:00:01 AM CEST if the `cycles_minting` feature is enabled in `icp_features`;
416    /// - 06 May 2021 21:17:10 CEST otherwise.
417    Timestamp(RawTime),
418    /// Configures the new instance to make progress automatically,
419    /// i.e., periodically update the time of the IC instance
420    /// to the real time and execute rounds on the subnets.
421    /// Creating the instance only returns after the certified time
422    /// of the IC instance has been updated for the first time.
423    AutoProgress(AutoProgressConfig),
424}
425
426#[derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize, Default)]
427pub(crate) struct InstanceConfig {
428    pub subnet_config_set: ExtendedSubnetConfigSet,
429    pub http_gateway_config: Option<InstanceHttpGatewayConfig>,
430    pub state_dir: Option<PathBuf>,
431    pub icp_features: Option<IcpFeatures>,
432    pub initial_time: Option<InitialTime>,
433}
434
435#[derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize, Default)]
436pub(crate) struct ExtendedSubnetConfigSet {
437    pub nns: Option<SubnetSpec>,
438    pub sns: Option<SubnetSpec>,
439    pub ii: Option<SubnetSpec>,
440    pub fiduciary: Option<SubnetSpec>,
441    pub bitcoin: Option<SubnetSpec>,
442    pub test_threshold_keys: Option<SubnetSpec>,
443    pub system: Vec<SubnetSpec>,
444    pub application: Vec<SubnetSpec>,
445    #[serde(default)]
446    pub cloud_engine: Vec<SubnetSpec>,
447    pub verified_application: Vec<SubnetSpec>,
448}
449
450/// The client creates new subnets with the server's production defaults.
451#[derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize)]
452pub(crate) struct SubnetSpec {
453    state_config: SubnetStateConfig,
454    instruction_config: SubnetInstructionConfig,
455    subnet_admins: Option<Vec<RawPrincipalId>>,
456    #[serde(default)]
457    cost_schedule: CanisterCyclesCostSchedule,
458}
459
460impl Default for SubnetSpec {
461    fn default() -> Self {
462        Self {
463            state_config: SubnetStateConfig::New,
464            instruction_config: SubnetInstructionConfig::Production,
465            subnet_admins: None,
466            cost_schedule: Default::default(),
467        }
468    }
469}
470
471/// Specifies instruction limits for canister execution on this subnet.
472#[derive(Debug, Clone, Eq, Hash, PartialEq, Ord, PartialOrd, Serialize, Deserialize)]
473pub enum SubnetInstructionConfig {
474    /// Use default instruction limits as in production.
475    Production,
476    /// Use very high instruction limits useful for asymptotic canister benchmarking.
477    Benchmarking,
478}
479
480/// Server representation for a fresh subnet.
481#[derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize)]
482enum SubnetStateConfig {
483    /// Create new subnet with empty state.
484    New,
485}
486
487/// Specifies how to charge canisters for their use of computational resources (such as
488/// executing instructions, storing data, network, etc.).
489#[derive(
490    Debug, Default, Hash, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize,
491)]
492pub enum CanisterCyclesCostSchedule {
493    #[default]
494    Normal,
495    Free,
496}
497
498/// Configuration details for a subnet, returned by PocketIc server
499#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)]
500pub struct SubnetConfig {
501    pub subnet_kind: SubnetKind,
502    pub subnet_admins: Option<Vec<RawPrincipalId>>,
503    pub cost_schedule: CanisterCyclesCostSchedule,
504    pub subnet_seed: [u8; 32],
505    /// Instruction limits for canister execution on this subnet.
506    pub instruction_config: SubnetInstructionConfig,
507    /// Some mainnet subnets have several disjunct canister ranges.
508    pub canister_ranges: Vec<CanisterIdRange>,
509}
510
511#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)]
512pub struct CanisterIdRange {
513    pub start: RawCanisterId,
514    pub end: RawCanisterId,
515}
516
517impl CanisterIdRange {
518    fn contains(&self, canister_id: Principal) -> bool {
519        Principal::from_slice(&self.start.canister_id) <= canister_id
520            && canister_id <= Principal::from_slice(&self.end.canister_id)
521    }
522}
523
524#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)]
525pub struct Topology {
526    pub subnet_configs: BTreeMap<SubnetId, SubnetConfig>,
527    pub default_effective_canister_id: RawCanisterId,
528}
529
530impl Topology {
531    pub fn get_subnet(&self, canister_id: Principal) -> Option<SubnetId> {
532        self.subnet_configs
533            .iter()
534            .find(|(_, config)| {
535                config
536                    .canister_ranges
537                    .iter()
538                    .any(|r| r.contains(canister_id))
539            })
540            .map(|(subnet_id, _)| subnet_id)
541            .copied()
542    }
543
544    pub fn get_app_subnets(&self) -> Vec<SubnetId> {
545        self.find_subnets(SubnetKind::Application, None)
546    }
547
548    pub fn get_cloud_engines(&self) -> Vec<SubnetId> {
549        self.find_subnets(SubnetKind::CloudEngine, None)
550    }
551
552    pub fn get_verified_app_subnets(&self) -> Vec<SubnetId> {
553        self.find_subnets(SubnetKind::VerifiedApplication, None)
554    }
555
556    pub fn get_benchmarking_app_subnets(&self) -> Vec<SubnetId> {
557        self.find_subnets(
558            SubnetKind::Application,
559            Some(SubnetInstructionConfig::Benchmarking),
560        )
561    }
562
563    pub fn get_bitcoin(&self) -> Option<SubnetId> {
564        self.find_subnet(SubnetKind::Bitcoin)
565    }
566
567    pub fn get_fiduciary(&self) -> Option<SubnetId> {
568        self.find_subnet(SubnetKind::Fiduciary)
569    }
570
571    pub fn get_ii(&self) -> Option<SubnetId> {
572        self.find_subnet(SubnetKind::II)
573    }
574
575    pub fn get_nns(&self) -> Option<SubnetId> {
576        self.find_subnet(SubnetKind::NNS)
577    }
578
579    pub fn get_sns(&self) -> Option<SubnetId> {
580        self.find_subnet(SubnetKind::SNS)
581    }
582
583    pub fn get_system_subnets(&self) -> Vec<SubnetId> {
584        self.find_subnets(SubnetKind::System, None)
585    }
586
587    fn find_subnets(
588        &self,
589        kind: SubnetKind,
590        instruction_config: Option<SubnetInstructionConfig>,
591    ) -> Vec<SubnetId> {
592        self.subnet_configs
593            .iter()
594            .filter(|(_, config)| {
595                config.subnet_kind == kind
596                    && instruction_config
597                        .as_ref()
598                        .map(|instruction_config| config.instruction_config == *instruction_config)
599                        .unwrap_or(true)
600            })
601            .map(|(id, _)| *id)
602            .collect()
603    }
604
605    fn find_subnet(&self, kind: SubnetKind) -> Option<SubnetId> {
606        self.subnet_configs
607            .iter()
608            .find(|(_, config)| config.subnet_kind == kind)
609            .map(|(id, _)| *id)
610    }
611}
612
613#[derive(Clone, Serialize, Deserialize, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)]
614pub enum CanisterHttpMethod {
615    GET,
616    POST,
617    HEAD,
618    PUT,
619    DELETE,
620    PATCH,
621}
622
623#[derive(Clone, Serialize, Deserialize, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)]
624pub struct CanisterHttpHeader {
625    pub name: String,
626    pub value: String,
627}
628
629/// How a canister HTTP outcall is replicated across the nodes of its subnet.
630#[derive(Clone, Serialize, Deserialize, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)]
631pub enum CanisterHttpReplication {
632    /// Every node of the subnet performs the outcall and a response is delivered
633    /// once `n - f` of them agree on it. Too many differing responses make the
634    /// outcall fail with a "no consensus could be reached" rejection instead.
635    /// Mock such an outcall with `PocketIc::mock_canister_http_response`, whose
636    /// `additional_responses` are what lets a test produce either outcome.
637    FullyReplicated,
638    /// One node of the subnet performs the outcall (`is_replicated = false`) and
639    /// its response is the one delivered. PocketIC does not report which node was
640    /// picked, so mock such an outcall with
641    /// `PocketIc::mock_canister_http_response` like a fully replicated one: give it
642    /// a single `response`, which every node then reports, and it is delivered no
643    /// matter which node was picked. Differing `additional_responses` would instead
644    /// make the delivered response unpredictable.
645    NonReplicated,
646    /// A committee of `total_requests` nodes performs the outcall and between
647    /// `min_responses` and `max_responses` of their (potentially differing)
648    /// responses are delivered to the calling canister. Mock such an outcall
649    /// by a caller with a flexible-outcall server adapter.
650    Flexible {
651        /// The number of nodes performing the outcall.
652        total_requests: u32,
653        /// The number of responses required to deliver a result.
654        min_responses: u32,
655        /// The largest number of responses that may be delivered.
656        max_responses: u32,
657    },
658}
659
660/// The pricing model applied to a canister HTTP outcall.
661#[derive(Clone, Serialize, Deserialize, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)]
662pub enum CanisterHttpPricingVersion {
663    /// The whole cost of the outcall is charged up front, based on the largest
664    /// response it could receive.
665    Legacy,
666    /// The outcall is charged for the resources it actually consumes: a base fee is
667    /// charged up front, a per-replica cycles allowance is withheld from the
668    /// payment, and whatever the responding nodes do not spend is refunded.
669    PayAsYouGo,
670}
671
672#[derive(Clone, Serialize, Deserialize, Debug)]
673pub struct RawCanisterHttpRequest {
674    pub subnet_id: RawSubnetId,
675    pub request_id: u64,
676    pub http_method: CanisterHttpMethod,
677    pub url: String,
678    pub headers: Vec<CanisterHttpHeader>,
679    #[serde(deserialize_with = "base64::deserialize")]
680    #[serde(serialize_with = "base64::serialize")]
681    pub body: Vec<u8>,
682    pub max_response_bytes: Option<u64>,
683    pub replication: CanisterHttpReplication,
684    pub pricing_version: CanisterHttpPricingVersion,
685}
686
687#[derive(Clone, Serialize, Deserialize, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)]
688pub struct CanisterHttpRequest {
689    pub subnet_id: Principal,
690    pub request_id: u64,
691    pub http_method: CanisterHttpMethod,
692    pub url: String,
693    pub headers: Vec<CanisterHttpHeader>,
694    #[serde(deserialize_with = "base64::deserialize")]
695    #[serde(serialize_with = "base64::serialize")]
696    pub body: Vec<u8>,
697    pub max_response_bytes: Option<u64>,
698    pub replication: CanisterHttpReplication,
699    pub pricing_version: CanisterHttpPricingVersion,
700}
701
702impl From<RawCanisterHttpRequest> for CanisterHttpRequest {
703    fn from(raw_canister_http_request: RawCanisterHttpRequest) -> Self {
704        Self {
705            subnet_id: candid::Principal::from_slice(
706                &raw_canister_http_request.subnet_id.subnet_id,
707            ),
708            request_id: raw_canister_http_request.request_id,
709            http_method: raw_canister_http_request.http_method,
710            url: raw_canister_http_request.url,
711            headers: raw_canister_http_request.headers,
712            body: raw_canister_http_request.body,
713            max_response_bytes: raw_canister_http_request.max_response_bytes,
714            replication: raw_canister_http_request.replication,
715            pricing_version: raw_canister_http_request.pricing_version,
716        }
717    }
718}
719
720impl From<CanisterHttpRequest> for RawCanisterHttpRequest {
721    fn from(canister_http_request: CanisterHttpRequest) -> Self {
722        Self {
723            subnet_id: canister_http_request.subnet_id.into(),
724            request_id: canister_http_request.request_id,
725            http_method: canister_http_request.http_method,
726            url: canister_http_request.url,
727            headers: canister_http_request.headers,
728            body: canister_http_request.body,
729            max_response_bytes: canister_http_request.max_response_bytes,
730            replication: canister_http_request.replication,
731            pricing_version: canister_http_request.pricing_version,
732        }
733    }
734}
735
736#[derive(Clone, Serialize, Deserialize, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)]
737pub struct CanisterHttpReply {
738    pub status: u16,
739    pub headers: Vec<CanisterHttpHeader>,
740    #[serde(deserialize_with = "base64::deserialize")]
741    #[serde(serialize_with = "base64::serialize")]
742    pub body: Vec<u8>,
743}
744
745#[derive(Clone, Serialize, Deserialize, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)]
746pub struct CanisterHttpReject {
747    pub reject_code: u64,
748    /// Bounded by the 1 KiB a node truncates its reject messages to.
749    pub message: String,
750}
751
752#[derive(Clone, Serialize, Deserialize, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)]
753pub enum CanisterHttpResponse {
754    CanisterHttpReply(CanisterHttpReply),
755    CanisterHttpReject(CanisterHttpReject),
756}
757
758#[derive(Clone, Serialize, Deserialize, Debug)]
759pub struct RawMockCanisterHttpResponse {
760    pub subnet_id: RawSubnetId,
761    pub request_id: u64,
762    pub response: CanisterHttpResponse,
763    pub additional_responses: Vec<CanisterHttpResponse>,
764}
765
766/// Mocked responses to a pending canister HTTP outcall made through the
767/// `http_request` management canister endpoint — whether fully replicated or
768/// non-replicated (see [`CanisterHttpReplication`]). A *flexible* outcall is
769/// answered by a caller with a flexible-outcall server adapter instead.
770///
771/// This answers for every node of the subnet, which is what an `http_request`
772/// outcall needs: a fully replicated one is performed by all of them, and a
773/// non-replicated one by a single node that is not exposed. It is therefore also
774/// the only shape that can make the nodes of a fully replicated outcall disagree
775/// (see `additional_responses`).
776///
777/// An `http_request` outcall answered this way reports one of:
778/// - the mocked response — as a reply, or as a rejection of the
779///   calling canister's call carrying the mocked code and message;
780/// - `SysTransient` and "No consensus could be reached" if its nodes disagree (see
781///   `additional_responses`; a non-replicated outcall has no such outcome, it just
782///   delivers its designated node's response);
783/// - `SysTransient` and "Out of cycles" if what the nodes left unspent of their
784///   per-replica cycles allowances no longer covers putting a response into a
785///   block;
786/// - `SysTransient` and "Canister http request timed out" once the outcall is
787///   older than its 60 second timeout.
788#[derive(Clone, Serialize, Deserialize, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)]
789pub struct MockCanisterHttpResponse {
790    pub subnet_id: Principal,
791    pub request_id: u64,
792    /// The response every node of the subnet reports, unless
793    /// `additional_responses` is non-empty — in which case this is the response of
794    /// one node and `additional_responses` holds the rest.
795    ///
796    /// With this response alone, all nodes agree, so a fully replicated outcall
797    /// delivers it (see [`CanisterHttpReplication::FullyReplicated`]).
798    pub response: CanisterHttpResponse,
799    /// The responses of the remaining nodes of the subnet. Either empty — then
800    /// `response` is used for every node — or exactly one short of the subnet size,
801    /// so that together with `response` there is one response per node.
802    ///
803    /// This is how a test makes the nodes of a *fully replicated* outcall disagree:
804    /// unless `n - f` of the `n` responses are equal, it fails with a "no consensus
805    /// could be reached" rejection.
806    ///
807    /// A *non-replicated* outcall has nothing to disagree about — only its
808    /// designated node's response is delivered, and PocketIC does not report which
809    /// node that is — so differing responses merely make the delivered one
810    /// unpredictable. Leave this empty unless the disagreement is what is under
811    /// test.
812    pub additional_responses: Vec<CanisterHttpResponse>,
813}
814
815impl From<RawMockCanisterHttpResponse> for MockCanisterHttpResponse {
816    fn from(raw_mock_canister_http_response: RawMockCanisterHttpResponse) -> Self {
817        Self {
818            subnet_id: candid::Principal::from_slice(
819                &raw_mock_canister_http_response.subnet_id.subnet_id,
820            ),
821            request_id: raw_mock_canister_http_response.request_id,
822            response: raw_mock_canister_http_response.response,
823            additional_responses: raw_mock_canister_http_response.additional_responses,
824        }
825    }
826}
827
828impl From<MockCanisterHttpResponse> for RawMockCanisterHttpResponse {
829    fn from(mock_canister_http_response: MockCanisterHttpResponse) -> Self {
830        Self {
831            subnet_id: RawSubnetId {
832                subnet_id: mock_canister_http_response.subnet_id.as_slice().to_vec(),
833            },
834            request_id: mock_canister_http_response.request_id,
835            response: mock_canister_http_response.response,
836            additional_responses: mock_canister_http_response.additional_responses,
837        }
838    }
839}
840
841#[derive(Clone, Serialize, Deserialize, Debug)]
842pub struct RawCanisterSnapshotDownload {
843    pub sender: RawPrincipalId,
844    pub canister_id: RawCanisterId,
845    #[serde(deserialize_with = "base64::deserialize")]
846    #[serde(serialize_with = "base64::serialize")]
847    pub snapshot_id: Vec<u8>,
848    pub snapshot_dir: PathBuf,
849}
850
851#[derive(Clone, Serialize, Deserialize, Debug)]
852pub struct RawCanisterSnapshotUpload {
853    pub sender: RawPrincipalId,
854    pub canister_id: RawCanisterId,
855    pub replace_snapshot: Option<RawCanisterSnapshotId>,
856    pub snapshot_dir: PathBuf,
857}
858
859#[derive(Clone, Serialize, Deserialize, Debug)]
860pub struct RawCanisterSnapshotId {
861    #[serde(deserialize_with = "base64::deserialize")]
862    #[serde(serialize_with = "base64::serialize")]
863    pub snapshot_id: Vec<u8>,
864}