1use 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::IntoEnumIterator;
16use strum_macros::EnumIter;
17
18pub type InstanceId = usize;
19
20#[derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize, JsonSchema)]
21pub struct AutoProgressConfig {
22 pub artificial_delay_ms: Option<u64>,
23}
24
25#[derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize, JsonSchema)]
26pub enum HttpGatewayBackend {
27 Replica(String),
28 PocketIcInstance(InstanceId),
29}
30
31#[derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize, JsonSchema)]
32pub struct HttpsConfig {
33 pub cert_path: String,
34 pub key_path: String,
35}
36
37#[derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize, JsonSchema)]
38pub struct InstanceHttpGatewayConfig {
39 pub ip_addr: Option<String>,
40 pub port: Option<u16>,
41 pub domains: Option<Vec<String>>,
42 pub https_config: Option<HttpsConfig>,
43 pub domain_custom_provider_local_file: Option<String>,
44}
45
46#[derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize, JsonSchema)]
47pub struct HttpGatewayConfig {
48 pub ip_addr: Option<String>,
49 pub port: Option<u16>,
50 pub forward_to: HttpGatewayBackend,
51 pub domains: Option<Vec<String>>,
52 pub https_config: Option<HttpsConfig>,
53 pub domain_custom_provider_local_file: Option<String>,
54}
55
56#[derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize, JsonSchema)]
57pub struct HttpGatewayDetails {
58 pub instance_id: InstanceId,
59 pub port: u16,
60 pub forward_to: HttpGatewayBackend,
61 pub domains: Option<Vec<String>>,
62 pub https_config: Option<HttpsConfig>,
63}
64
65#[derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize, JsonSchema)]
66pub struct HttpGatewayInfo {
67 pub instance_id: InstanceId,
68 pub port: u16,
69}
70
71#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
72pub enum CreateHttpGatewayResponse {
73 Created(HttpGatewayInfo),
74 Error { message: String },
75}
76
77#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
78pub enum CreateInstanceResponse {
79 Created {
80 instance_id: InstanceId,
81 topology: Topology,
82 http_gateway_info: Option<HttpGatewayInfo>,
83 },
84 Error {
85 message: String,
86 },
87}
88
89#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq, Serialize, Deserialize, JsonSchema)]
90pub struct RawTime {
91 pub nanos_since_epoch: u64,
92}
93
94#[derive(Clone, Serialize, Deserialize, Debug, JsonSchema, PartialEq, Eq, Hash)]
100pub enum RawEffectivePrincipal {
101 None,
102 SubnetId(
103 #[serde(deserialize_with = "base64::deserialize")]
104 #[serde(serialize_with = "base64::serialize")]
105 Vec<u8>,
106 ),
107 CanisterId(
108 #[serde(deserialize_with = "base64::deserialize")]
109 #[serde(serialize_with = "base64::serialize")]
110 Vec<u8>,
111 ),
112}
113
114impl std::fmt::Display for RawEffectivePrincipal {
115 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
116 match self {
117 RawEffectivePrincipal::None => write!(f, "None"),
118 RawEffectivePrincipal::SubnetId(subnet_id) => {
119 let principal = Principal::from_slice(subnet_id);
120 write!(f, "SubnetId({principal})")
121 }
122 RawEffectivePrincipal::CanisterId(canister_id) => {
123 let principal = Principal::from_slice(canister_id);
124 write!(f, "CanisterId({principal})")
125 }
126 }
127 }
128}
129
130#[derive(Clone, Serialize, Deserialize, Debug, JsonSchema)]
131pub struct RawMessageId {
132 pub effective_principal: RawEffectivePrincipal,
133 #[serde(deserialize_with = "base64::deserialize")]
134 #[serde(serialize_with = "base64::serialize")]
135 pub message_id: Vec<u8>,
136}
137
138#[derive(Clone, Serialize, Deserialize, Debug, JsonSchema)]
139pub struct RawIngressStatusArgs {
140 pub raw_message_id: RawMessageId,
141 pub raw_caller: Option<RawPrincipalId>,
142}
143
144#[derive(Clone, Serialize, Deserialize, Debug, JsonSchema)]
145pub struct RawSenderInfo {
146 #[serde(deserialize_with = "base64::deserialize")]
147 #[serde(serialize_with = "base64::serialize")]
148 pub info: Vec<u8>,
149 #[serde(deserialize_with = "base64::deserialize")]
150 #[serde(serialize_with = "base64::serialize")]
151 pub signer: Vec<u8>,
152}
153
154#[derive(Clone, Serialize, Deserialize, Debug, JsonSchema)]
155pub struct RawCanisterCall {
156 #[serde(deserialize_with = "base64::deserialize")]
157 #[serde(serialize_with = "base64::serialize")]
158 pub sender: Vec<u8>,
159 #[serde(deserialize_with = "base64::deserialize")]
160 #[serde(serialize_with = "base64::serialize")]
161 pub canister_id: Vec<u8>,
162 pub effective_principal: RawEffectivePrincipal,
163 pub method: String,
164 #[serde(deserialize_with = "base64::deserialize")]
165 #[serde(serialize_with = "base64::serialize")]
166 pub payload: Vec<u8>,
167 #[serde(skip_serializing_if = "Option::is_none")]
168 pub sender_info: Option<RawSenderInfo>,
169}
170
171#[derive(Clone, Serialize, Deserialize, Debug, JsonSchema)]
172pub enum RawCanisterResult {
173 Ok(
174 #[serde(deserialize_with = "base64::deserialize")]
175 #[serde(serialize_with = "base64::serialize")]
176 Vec<u8>,
177 ),
178 Err(RejectResponse),
179}
180
181impl From<Result<Vec<u8>, RejectResponse>> for RawCanisterResult {
182 fn from(result: Result<Vec<u8>, RejectResponse>) -> Self {
183 match result {
184 Ok(data) => RawCanisterResult::Ok(data),
185 Err(reject_response) => RawCanisterResult::Err(reject_response),
186 }
187 }
188}
189
190impl From<RawCanisterResult> for Result<Vec<u8>, RejectResponse> {
191 fn from(result: RawCanisterResult) -> Self {
192 match result {
193 RawCanisterResult::Ok(data) => Ok(data),
194 RawCanisterResult::Err(reject_response) => Err(reject_response),
195 }
196 }
197}
198
199#[derive(Clone, Serialize, Deserialize, Debug, JsonSchema)]
200pub struct RawSetStableMemory {
201 #[serde(deserialize_with = "base64::deserialize")]
202 #[serde(serialize_with = "base64::serialize")]
203 pub canister_id: Vec<u8>,
204 pub blob_id: BlobId,
205}
206
207#[derive(Clone, Serialize, Deserialize, Debug, JsonSchema)]
208pub struct RawStableMemory {
209 #[serde(deserialize_with = "base64::deserialize")]
210 #[serde(serialize_with = "base64::serialize")]
211 pub blob: Vec<u8>,
212}
213
214#[derive(Clone, Serialize, Deserialize, Debug, JsonSchema)]
215pub struct ApiError {
216 message: String,
217}
218
219#[derive(Clone, Serialize, Deserialize, Debug, JsonSchema)]
220pub struct StartedOrBusyResponse {
221 pub state_label: String,
222 pub op_id: String,
223}
224
225#[derive(Clone, Serialize, Deserialize, Debug, JsonSchema)]
226#[serde(untagged)]
227pub enum ApiResponse<T> {
228 Success(T),
229 Busy { state_label: String, op_id: String },
230 Started { state_label: String, op_id: String },
231 Error { message: String },
232}
233
234impl<T: DeserializeOwned> ApiResponse<T> {
235 pub async fn from_response(resp: Response) -> Self {
236 match resp.status() {
237 reqwest::StatusCode::OK => {
238 let result = resp.json::<T>().await;
239 match result {
240 Ok(t) => ApiResponse::Success(t),
241 Err(e) => ApiResponse::Error {
242 message: format!("Could not parse response: {e}"),
243 },
244 }
245 }
246 reqwest::StatusCode::ACCEPTED => {
247 let result = resp.json::<StartedOrBusyResponse>().await;
248 match result {
249 Ok(StartedOrBusyResponse { state_label, op_id }) => {
250 ApiResponse::Started { state_label, op_id }
251 }
252 Err(e) => ApiResponse::Error {
253 message: format!("Could not parse response: {e}"),
254 },
255 }
256 }
257 reqwest::StatusCode::CONFLICT => {
258 let result = resp.json::<StartedOrBusyResponse>().await;
259 match result {
260 Ok(StartedOrBusyResponse { state_label, op_id }) => {
261 ApiResponse::Busy { state_label, op_id }
262 }
263 Err(e) => ApiResponse::Error {
264 message: format!("Could not parse response: {e}"),
265 },
266 }
267 }
268 _ => {
269 let result = resp.json::<ApiError>().await;
270 match result {
271 Ok(e) => ApiResponse::Error { message: e.message },
272 Err(e) => ApiResponse::Error {
273 message: format!("Could not parse error: {e}"),
274 },
275 }
276 }
277 }
278 }
279}
280
281#[derive(Clone, Serialize, Deserialize, Debug, JsonSchema)]
282pub struct RawAddCycles {
283 #[serde(deserialize_with = "base64::deserialize")]
284 #[serde(serialize_with = "base64::serialize")]
285 pub canister_id: Vec<u8>,
286 pub amount: u128,
287}
288
289#[derive(Clone, Serialize, Deserialize, Debug, JsonSchema)]
290pub struct RawCycles {
291 pub cycles: u128,
292}
293
294#[derive(
295 Clone, Serialize, Hash, Eq, PartialEq, Ord, PartialOrd, Deserialize, Debug, JsonSchema,
296)]
297pub struct RawPrincipalId {
298 #[serde(deserialize_with = "base64::deserialize")]
300 #[serde(serialize_with = "base64::serialize")]
301 pub principal_id: Vec<u8>,
302}
303
304impl From<Principal> for RawPrincipalId {
305 fn from(principal: Principal) -> Self {
306 Self {
307 principal_id: principal.as_slice().to_vec(),
308 }
309 }
310}
311
312impl From<RawPrincipalId> for Principal {
313 fn from(raw_principal_id: RawPrincipalId) -> Self {
314 Principal::from_slice(&raw_principal_id.principal_id)
315 }
316}
317
318#[derive(Clone, Serialize, Eq, PartialEq, Ord, PartialOrd, Deserialize, Debug, JsonSchema)]
319pub struct RawCanisterId {
320 #[serde(deserialize_with = "base64::deserialize")]
322 #[serde(serialize_with = "base64::serialize")]
323 pub canister_id: Vec<u8>,
324}
325
326impl From<Principal> for RawCanisterId {
327 fn from(principal: Principal) -> Self {
328 Self {
329 canister_id: principal.as_slice().to_vec(),
330 }
331 }
332}
333
334impl From<RawCanisterId> for Principal {
335 fn from(raw_canister_id: RawCanisterId) -> Self {
336 Principal::from_slice(&raw_canister_id.canister_id)
337 }
338}
339
340#[derive(Clone, Serialize, Deserialize, Debug, JsonSchema, PartialEq, Eq, Hash)]
341pub struct RawSubnetId {
342 #[serde(deserialize_with = "base64::deserialize")]
343 #[serde(serialize_with = "base64::serialize")]
344 pub subnet_id: Vec<u8>,
345}
346
347pub type SubnetId = Principal;
348
349impl From<Principal> for RawSubnetId {
350 fn from(principal: Principal) -> Self {
351 Self {
352 subnet_id: principal.as_slice().to_vec(),
353 }
354 }
355}
356
357impl From<RawSubnetId> for Principal {
358 fn from(val: RawSubnetId) -> Self {
359 Principal::from_slice(&val.subnet_id)
360 }
361}
362
363#[derive(
364 Clone, Serialize, Deserialize, Debug, JsonSchema, PartialEq, Eq, PartialOrd, Ord, Hash,
365)]
366pub struct RawNodeId {
367 #[serde(deserialize_with = "base64::deserialize")]
368 #[serde(serialize_with = "base64::serialize")]
369 pub node_id: Vec<u8>,
370}
371
372impl From<RawNodeId> for Principal {
373 fn from(val: RawNodeId) -> Self {
374 Principal::from_slice(&val.node_id)
375 }
376}
377
378impl From<Principal> for RawNodeId {
379 fn from(principal: Principal) -> Self {
380 Self {
381 node_id: principal.as_slice().to_vec(),
382 }
383 }
384}
385
386#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, JsonSchema, Default)]
387pub struct RawTickConfigs {
388 pub blockmakers: Option<Vec<RawSubnetBlockmakers>>,
389}
390
391#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, JsonSchema)]
392pub struct RawSubnetBlockmakers {
393 pub subnet: RawSubnetId,
394 pub blockmaker: RawNodeId,
395 pub failed_blockmakers: Vec<RawNodeId>,
396}
397
398#[derive(Serialize, Deserialize, JsonSchema)]
399pub struct RawVerifyCanisterSigArg {
400 #[serde(deserialize_with = "base64::deserialize")]
401 #[serde(serialize_with = "base64::serialize")]
402 pub msg: Vec<u8>,
403 #[serde(deserialize_with = "base64::deserialize")]
404 #[serde(serialize_with = "base64::serialize")]
405 pub sig: Vec<u8>,
406 #[serde(deserialize_with = "base64::deserialize")]
407 #[serde(serialize_with = "base64::serialize")]
408 pub pubkey: Vec<u8>,
409 #[serde(deserialize_with = "base64::deserialize")]
410 #[serde(serialize_with = "base64::serialize")]
411 pub root_pubkey: Vec<u8>,
412}
413
414#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize, JsonSchema)]
415pub struct BlobId(
416 #[serde(deserialize_with = "base64::deserialize")]
417 #[serde(serialize_with = "base64::serialize")]
418 pub Vec<u8>,
419);
420
421impl std::fmt::Display for BlobId {
422 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
423 write!(f, "BlobId{{{}}}", hex::encode(self.0.clone()))
424 }
425}
426
427#[derive(Clone, Debug)]
428pub struct BinaryBlob {
429 pub data: Vec<u8>,
430 pub compression: BlobCompression,
431}
432
433#[derive(Clone, Copy, Debug, Eq, PartialEq, JsonSchema)]
434pub enum BlobCompression {
435 Gzip,
436 NoCompression,
437}
438
439pub mod base64 {
442 use ::base64::prelude::*;
443 use serde::{Deserialize, Serialize};
444 use serde::{Deserializer, Serializer};
445
446 pub fn serialize<S: Serializer>(v: &Vec<u8>, s: S) -> Result<S::Ok, S::Error> {
447 let base64 = BASE64_STANDARD.encode(v);
448 String::serialize(&base64, s)
449 }
450
451 pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<Vec<u8>, D::Error> {
452 let base64 = String::deserialize(d)?;
453 BASE64_STANDARD
454 .decode(base64.as_bytes())
455 .map_err(serde::de::Error::custom)
456 }
457}
458
459#[derive(
462 Debug,
463 Clone,
464 Copy,
465 Eq,
466 Hash,
467 PartialEq,
468 Ord,
469 PartialOrd,
470 Serialize,
471 Deserialize,
472 JsonSchema,
473 EnumIter,
474)]
475pub enum SubnetKind {
476 Application,
477 Bitcoin,
478 CloudEngine,
479 Fiduciary,
480 II,
481 NNS,
482 SNS,
483 System,
484 TestThresholdKeys,
485 VerifiedApplication,
486}
487
488impl SubnetKind {
489 pub fn is_named(self) -> bool {
490 NamedSubnet::try_from(self).is_ok()
491 }
492}
493
494#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq, Serialize, Deserialize, JsonSchema, EnumIter)]
497pub enum NamedSubnet {
498 NNS,
499 SNS,
500 II,
501 Fiduciary,
502 Bitcoin,
503 TestThresholdKeys,
504}
505
506impl From<NamedSubnet> for SubnetKind {
507 fn from(named: NamedSubnet) -> Self {
508 match named {
509 NamedSubnet::NNS => SubnetKind::NNS,
510 NamedSubnet::SNS => SubnetKind::SNS,
511 NamedSubnet::II => SubnetKind::II,
512 NamedSubnet::Fiduciary => SubnetKind::Fiduciary,
513 NamedSubnet::Bitcoin => SubnetKind::Bitcoin,
514 NamedSubnet::TestThresholdKeys => SubnetKind::TestThresholdKeys,
515 }
516 }
517}
518
519impl TryFrom<SubnetKind> for NamedSubnet {
523 type Error = ();
524 fn try_from(kind: SubnetKind) -> Result<Self, Self::Error> {
525 match kind {
526 SubnetKind::NNS => Ok(NamedSubnet::NNS),
527 SubnetKind::SNS => Ok(NamedSubnet::SNS),
528 SubnetKind::II => Ok(NamedSubnet::II),
529 SubnetKind::Fiduciary => Ok(NamedSubnet::Fiduciary),
530 SubnetKind::Bitcoin => Ok(NamedSubnet::Bitcoin),
531 SubnetKind::TestThresholdKeys => Ok(NamedSubnet::TestThresholdKeys),
532 SubnetKind::Application
533 | SubnetKind::CloudEngine
534 | SubnetKind::System
535 | SubnetKind::VerifiedApplication => Err(()),
536 }
537 }
538}
539
540#[derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize, Default, JsonSchema)]
543pub struct SubnetConfigSet {
544 pub nns: bool,
545 pub sns: bool,
546 pub ii: bool,
547 pub fiduciary: bool,
548 pub bitcoin: bool,
549 pub test_threshold_keys: bool,
550 pub system: usize,
551 pub application: usize,
552 pub cloud_engine: usize,
553 pub verified_application: usize,
554}
555
556impl SubnetConfigSet {
557 pub fn validate(&self) -> Result<(), String> {
558 if self.system > 0
559 || self.application > 0
560 || self.cloud_engine > 0
561 || self.verified_application > 0
562 || self.nns
563 || self.sns
564 || self.ii
565 || self.fiduciary
566 || self.bitcoin
567 || self.test_threshold_keys
568 {
569 return Ok(());
570 }
571 Err("SubnetConfigSet must contain at least one subnet".to_owned())
572 }
573}
574
575impl From<SubnetConfigSet> for ExtendedSubnetConfigSet {
576 fn from(
577 SubnetConfigSet {
578 nns,
579 sns,
580 ii,
581 fiduciary: fid,
582 bitcoin,
583 test_threshold_keys,
584 system,
585 application,
586 cloud_engine,
587 verified_application,
588 }: SubnetConfigSet,
589 ) -> Self {
590 ExtendedSubnetConfigSet {
591 nns: if nns {
592 Some(SubnetSpec::default())
593 } else {
594 None
595 },
596 sns: if sns {
597 Some(SubnetSpec::default())
598 } else {
599 None
600 },
601 ii: if ii {
602 Some(SubnetSpec::default())
603 } else {
604 None
605 },
606 fiduciary: if fid {
607 Some(SubnetSpec::default())
608 } else {
609 None
610 },
611 bitcoin: if bitcoin {
612 Some(SubnetSpec::default())
613 } else {
614 None
615 },
616 test_threshold_keys: if test_threshold_keys {
617 Some(SubnetSpec::default())
618 } else {
619 None
620 },
621 system: vec![SubnetSpec::default(); system],
622 application: vec![SubnetSpec::default(); application],
623 cloud_engine: vec![SubnetSpec::default(); cloud_engine],
624 verified_application: vec![SubnetSpec::default(); verified_application],
625 }
626 }
627}
628
629#[derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize, JsonSchema)]
630pub enum IcpConfigFlag {
631 Disabled,
632 Enabled,
633}
634
635#[derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize, Default, JsonSchema)]
637pub struct IcpConfig {
638 pub beta_features: Option<IcpConfigFlag>,
640 pub function_name_length_limits: Option<IcpConfigFlag>,
642 pub canister_execution_rate_limiting: Option<IcpConfigFlag>,
645}
646
647#[derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize, Default, JsonSchema)]
648pub enum IcpFeaturesConfig {
649 #[default]
651 DefaultConfig,
652}
653
654#[derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize, Default, JsonSchema)]
663pub struct IcpFeatures {
664 pub registry: Option<IcpFeaturesConfig>,
667 pub cycles_minting: Option<IcpFeaturesConfig>,
671 pub icp_token: Option<IcpFeaturesConfig>,
674 pub cycles_token: Option<IcpFeaturesConfig>,
677 pub nns_governance: Option<IcpFeaturesConfig>,
681 pub sns: Option<IcpFeaturesConfig>,
685 pub ii: Option<IcpFeaturesConfig>,
688 pub nns_ui: Option<IcpFeaturesConfig>,
692 pub bitcoin: Option<IcpFeaturesConfig>,
695 pub dogecoin: Option<IcpFeaturesConfig>,
698 pub canister_migration: Option<IcpFeaturesConfig>,
701}
702
703#[derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize, JsonSchema)]
704pub enum InitialTime {
705 Timestamp(RawTime),
709 AutoProgress(AutoProgressConfig),
715}
716
717#[derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize, Default, JsonSchema)]
718pub enum IncompleteStateFlag {
719 #[default]
720 Disabled,
721 Enabled,
722}
723
724#[derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize, Default, JsonSchema)]
725pub struct InstanceConfig {
726 pub subnet_config_set: ExtendedSubnetConfigSet,
727 pub http_gateway_config: Option<InstanceHttpGatewayConfig>,
728 pub state_dir: Option<PathBuf>,
729 pub icp_config: Option<IcpConfig>,
730 pub log_level: Option<String>,
731 pub bitcoind_addr: Option<Vec<SocketAddr>>,
732 pub dogecoind_addr: Option<Vec<SocketAddr>>,
733 pub icp_features: Option<IcpFeatures>,
734 pub incomplete_state: Option<IncompleteStateFlag>,
735 pub initial_time: Option<InitialTime>,
736 pub mainnet_nns_subnet_id: Option<bool>,
737 pub disable_ingress_validation: Option<bool>,
738}
739
740#[derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize, Default, JsonSchema)]
741pub struct ExtendedSubnetConfigSet {
742 pub nns: Option<SubnetSpec>,
743 pub sns: Option<SubnetSpec>,
744 pub ii: Option<SubnetSpec>,
745 pub fiduciary: Option<SubnetSpec>,
746 pub bitcoin: Option<SubnetSpec>,
747 pub test_threshold_keys: Option<SubnetSpec>,
748 pub system: Vec<SubnetSpec>,
749 pub application: Vec<SubnetSpec>,
750 #[serde(default)]
751 pub cloud_engine: Vec<SubnetSpec>,
752 pub verified_application: Vec<SubnetSpec>,
753}
754
755#[derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize, JsonSchema)]
757pub struct SubnetSpec {
758 state_config: SubnetStateConfig,
759 instruction_config: SubnetInstructionConfig,
760 subnet_admins: Option<Vec<RawPrincipalId>>,
761 #[serde(default)]
762 cost_schedule: CanisterCyclesCostSchedule,
763}
764
765impl SubnetSpec {
766 pub fn with_state_dir(mut self, path: PathBuf) -> SubnetSpec {
767 self.state_config = SubnetStateConfig::FromPath(path);
768 self
769 }
770
771 pub fn with_benchmarking_instruction_config(mut self) -> SubnetSpec {
772 self.instruction_config = SubnetInstructionConfig::Benchmarking;
773 self
774 }
775
776 pub fn get_state_path(&self) -> Option<PathBuf> {
777 self.state_config.get_path()
778 }
779
780 pub fn get_instruction_config(&self) -> SubnetInstructionConfig {
781 self.instruction_config.clone()
782 }
783
784 pub fn is_supported(&self) -> bool {
785 match &self.state_config {
786 SubnetStateConfig::New => true,
787 SubnetStateConfig::FromPath(..) => true,
788 SubnetStateConfig::FromBlobStore(..) => false,
789 }
790 }
791
792 pub fn with_subnet_admins(mut self, subnet_admins: Vec<Principal>) -> SubnetSpec {
793 self.subnet_admins = Some(
794 subnet_admins
795 .into_iter()
796 .map(RawPrincipalId::from)
797 .collect(),
798 );
799 self
800 }
801
802 pub fn with_cost_schedule(mut self, cost_schedule: CanisterCyclesCostSchedule) -> SubnetSpec {
803 self.cost_schedule = cost_schedule;
804 self
805 }
806
807 pub fn get_subnet_admins(&self) -> Option<Vec<Principal>> {
808 self.subnet_admins
809 .clone()
810 .map(|subnet_admins| subnet_admins.into_iter().map(Principal::from).collect())
811 }
812
813 pub fn get_cost_schedule(&self) -> CanisterCyclesCostSchedule {
814 self.cost_schedule
815 }
816}
817
818impl Default for SubnetSpec {
819 fn default() -> Self {
820 Self {
821 state_config: SubnetStateConfig::New,
822 instruction_config: SubnetInstructionConfig::Production,
823 subnet_admins: None,
824 cost_schedule: Default::default(),
825 }
826 }
827}
828
829#[derive(
831 Debug, Clone, Eq, Hash, PartialEq, Ord, PartialOrd, Serialize, Deserialize, JsonSchema,
832)]
833pub enum SubnetInstructionConfig {
834 Production,
836 Benchmarking,
838}
839
840#[derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize, JsonSchema)]
843pub enum SubnetStateConfig {
844 New,
846 FromPath(PathBuf),
849 FromBlobStore(BlobId),
852}
853
854impl SubnetStateConfig {
855 pub fn get_path(&self) -> Option<PathBuf> {
856 match self {
857 SubnetStateConfig::FromPath(path) => Some(path.clone()),
858 SubnetStateConfig::FromBlobStore(_) => None,
859 SubnetStateConfig::New => None,
860 }
861 }
862}
863
864impl ExtendedSubnetConfigSet {
865 fn named_subnet_spec(&self, named: NamedSubnet) -> Option<SubnetSpec> {
866 match named {
867 NamedSubnet::NNS => self.nns.clone(),
868 NamedSubnet::SNS => self.sns.clone(),
869 NamedSubnet::II => self.ii.clone(),
870 NamedSubnet::Fiduciary => self.fiduciary.clone(),
871 NamedSubnet::Bitcoin => self.bitcoin.clone(),
872 NamedSubnet::TestThresholdKeys => self.test_threshold_keys.clone(),
873 }
874 }
875
876 #[allow(clippy::type_complexity)]
878 pub fn get_named(&self) -> Vec<(SubnetKind, Option<PathBuf>, SubnetInstructionConfig)> {
879 NamedSubnet::iter()
880 .filter_map(|named| {
881 self.named_subnet_spec(named).map(|spec| {
882 (
883 SubnetKind::from(named),
884 spec.get_state_path(),
885 spec.get_instruction_config(),
886 )
887 })
888 })
889 .collect()
890 }
891
892 pub fn validate(&self) -> Result<(), String> {
893 let all_but_application_and_cloud_engine = self
894 .nns
895 .iter()
896 .chain(&self.sns)
897 .chain(&self.ii)
898 .chain(&self.fiduciary)
899 .chain(&self.bitcoin)
900 .chain(&self.test_threshold_keys)
901 .chain(&self.system)
902 .chain(&self.verified_application);
903
904 let all = all_but_application_and_cloud_engine
905 .clone()
906 .chain(&self.application)
907 .chain(&self.cloud_engine);
908
909 if all_but_application_and_cloud_engine
911 .clone()
912 .any(|spec| spec.subnet_admins.is_some())
913 {
914 return Err(
915 "Subnet admins can only be specified for subnet of kind `Application` or `CloudEngine`".into(),
916 );
917 }
918
919 if all_but_application_and_cloud_engine
921 .clone()
922 .any(|spec| spec.cost_schedule != Default::default())
923 {
924 return Err("Non-default cost schedule can only be specified for subnet of kind `Application` or `CloudEngine`".into());
925 }
926
927 if all.clone().any(|spec| {
929 spec.subnet_admins.is_some() && spec.cost_schedule != CanisterCyclesCostSchedule::Free
930 }) {
931 return Err(
932 "Subnet admins can only be specified for subnet with cost schedule of kind `Free`"
933 .into(),
934 );
935 }
936
937 if self
939 .cloud_engine
940 .iter()
941 .any(|spec| spec.cost_schedule != CanisterCyclesCostSchedule::Free)
942 {
943 return Err(
944 "Every subnet of kind `CloudEngine` must have cost schedule of kind `Free`".into(),
945 );
946 }
947
948 let has_any = all.clone().next().is_some();
950
951 if !has_any {
952 return Err("ExtendedSubnetConfigSet must contain at least one subnet".into());
953 }
954
955 Ok(())
956 }
957
958 pub fn try_with_icp_features(mut self, icp_features: &IcpFeatures) -> Result<Self, String> {
959 let check_empty_subnet = |subnet: &Option<SubnetSpec>, subnet_desc, icp_feature| {
960 if let Some(config) = subnet
961 && !matches!(config.state_config, SubnetStateConfig::New)
962 {
963 return Err(format!(
964 "The {subnet_desc} subnet must be empty when specifying the `{icp_feature}` ICP feature."
965 ));
966 }
967 Ok(())
968 };
969 let IcpFeatures {
972 registry,
973 cycles_minting,
974 icp_token,
975 cycles_token,
976 nns_governance,
977 sns,
978 ii,
979 nns_ui,
980 bitcoin,
981 dogecoin,
982 canister_migration,
983 } = icp_features;
984 for (flag, icp_feature_str) in [
986 (registry, "registry"),
987 (cycles_minting, "cycles_minting"),
988 (icp_token, "icp_token"),
989 (nns_governance, "nns_governance"),
990 (sns, "sns"),
991 (nns_ui, "nns_ui"),
992 (canister_migration, "canister_migration"),
993 ] {
994 if flag.is_some() {
995 check_empty_subnet(&self.nns, "NNS", icp_feature_str)?;
996 self.nns = Some(self.nns.unwrap_or_default());
997 }
998 }
999 for (flag, icp_feature_str) in [(cycles_token, "cycles_token"), (ii, "ii")] {
1001 if flag.is_some() {
1002 check_empty_subnet(&self.ii, "II", icp_feature_str)?;
1003 self.ii = Some(self.ii.unwrap_or_default());
1004 }
1005 }
1006 if sns.is_some() {
1008 check_empty_subnet(&self.sns, "SNS", "sns")?;
1009 self.sns = Some(self.sns.unwrap_or_default());
1010 }
1011 for (flag, icp_feature_str) in [(bitcoin, "bitcoin"), (dogecoin, "dogecoin")] {
1013 if flag.is_some() {
1014 check_empty_subnet(&self.bitcoin, "Bitcoin", icp_feature_str)?;
1015 self.bitcoin = Some(self.bitcoin.unwrap_or_default());
1016 }
1017 }
1018 Ok(self)
1019 }
1020}
1021
1022#[derive(
1025 Debug,
1026 Default,
1027 Hash,
1028 Clone,
1029 Copy,
1030 Eq,
1031 PartialEq,
1032 Ord,
1033 PartialOrd,
1034 Serialize,
1035 Deserialize,
1036 JsonSchema,
1037)]
1038pub enum CanisterCyclesCostSchedule {
1039 #[default]
1040 Normal,
1041 Free,
1042}
1043
1044#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize, JsonSchema)]
1046pub struct SubnetConfig {
1047 pub subnet_kind: SubnetKind,
1048 pub subnet_admins: Option<Vec<RawPrincipalId>>,
1049 pub cost_schedule: CanisterCyclesCostSchedule,
1050 pub subnet_seed: [u8; 32],
1051 pub instruction_config: SubnetInstructionConfig,
1053 pub canister_ranges: Vec<CanisterIdRange>,
1055}
1056
1057#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize, JsonSchema)]
1058pub struct CanisterIdRange {
1059 pub start: RawCanisterId,
1060 pub end: RawCanisterId,
1061}
1062
1063impl CanisterIdRange {
1064 fn contains(&self, canister_id: Principal) -> bool {
1065 Principal::from_slice(&self.start.canister_id) <= canister_id
1066 && canister_id <= Principal::from_slice(&self.end.canister_id)
1067 }
1068}
1069
1070#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize, JsonSchema)]
1071pub struct Topology {
1072 pub subnet_configs: BTreeMap<SubnetId, SubnetConfig>,
1073 pub default_effective_canister_id: RawCanisterId,
1074}
1075
1076impl Topology {
1077 pub fn get_subnet(&self, canister_id: Principal) -> Option<SubnetId> {
1078 self.subnet_configs
1079 .iter()
1080 .find(|(_, config)| {
1081 config
1082 .canister_ranges
1083 .iter()
1084 .any(|r| r.contains(canister_id))
1085 })
1086 .map(|(subnet_id, _)| subnet_id)
1087 .copied()
1088 }
1089
1090 pub fn get_app_subnets(&self) -> Vec<SubnetId> {
1091 self.find_subnets(SubnetKind::Application, None)
1092 }
1093
1094 pub fn get_cloud_engines(&self) -> Vec<SubnetId> {
1095 self.find_subnets(SubnetKind::CloudEngine, None)
1096 }
1097
1098 pub fn get_verified_app_subnets(&self) -> Vec<SubnetId> {
1099 self.find_subnets(SubnetKind::VerifiedApplication, None)
1100 }
1101
1102 pub fn get_benchmarking_app_subnets(&self) -> Vec<SubnetId> {
1103 self.find_subnets(
1104 SubnetKind::Application,
1105 Some(SubnetInstructionConfig::Benchmarking),
1106 )
1107 }
1108
1109 pub fn get_bitcoin(&self) -> Option<SubnetId> {
1110 self.find_subnet(SubnetKind::Bitcoin)
1111 }
1112
1113 pub fn get_fiduciary(&self) -> Option<SubnetId> {
1114 self.find_subnet(SubnetKind::Fiduciary)
1115 }
1116
1117 pub fn get_ii(&self) -> Option<SubnetId> {
1118 self.find_subnet(SubnetKind::II)
1119 }
1120
1121 pub fn get_nns(&self) -> Option<SubnetId> {
1122 self.find_subnet(SubnetKind::NNS)
1123 }
1124
1125 pub fn get_sns(&self) -> Option<SubnetId> {
1126 self.find_subnet(SubnetKind::SNS)
1127 }
1128
1129 pub fn get_system_subnets(&self) -> Vec<SubnetId> {
1130 self.find_subnets(SubnetKind::System, None)
1131 }
1132
1133 fn find_subnets(
1134 &self,
1135 kind: SubnetKind,
1136 instruction_config: Option<SubnetInstructionConfig>,
1137 ) -> Vec<SubnetId> {
1138 self.subnet_configs
1139 .iter()
1140 .filter(|(_, config)| {
1141 config.subnet_kind == kind
1142 && instruction_config
1143 .as_ref()
1144 .map(|instruction_config| config.instruction_config == *instruction_config)
1145 .unwrap_or(true)
1146 })
1147 .map(|(id, _)| *id)
1148 .collect()
1149 }
1150
1151 fn find_subnet(&self, kind: SubnetKind) -> Option<SubnetId> {
1152 self.subnet_configs
1153 .iter()
1154 .find(|(_, config)| config.subnet_kind == kind)
1155 .map(|(id, _)| *id)
1156 }
1157}
1158
1159#[derive(
1160 Clone, Serialize, Deserialize, Debug, Hash, Eq, PartialEq, Ord, PartialOrd, JsonSchema,
1161)]
1162pub enum CanisterHttpMethod {
1163 GET,
1164 POST,
1165 HEAD,
1166 PUT,
1167 DELETE,
1168 PATCH,
1169}
1170
1171#[derive(
1172 Clone, Serialize, Deserialize, Debug, Hash, Eq, PartialEq, Ord, PartialOrd, JsonSchema,
1173)]
1174pub struct CanisterHttpHeader {
1175 pub name: String,
1176 pub value: String,
1177}
1178
1179#[derive(
1181 Clone, Serialize, Deserialize, Debug, Hash, Eq, PartialEq, Ord, PartialOrd, JsonSchema,
1182)]
1183pub enum CanisterHttpReplication {
1184 FullyReplicated,
1190 NonReplicated,
1198 Flexible {
1203 total_requests: u32,
1205 min_responses: u32,
1207 max_responses: u32,
1209 },
1210}
1211
1212#[derive(
1214 Clone, Serialize, Deserialize, Debug, Hash, Eq, PartialEq, Ord, PartialOrd, JsonSchema,
1215)]
1216pub enum CanisterHttpPricingVersion {
1217 Legacy,
1220 PayAsYouGo,
1224}
1225
1226#[derive(Clone, Serialize, Deserialize, Debug, JsonSchema)]
1227pub struct RawCanisterHttpRequest {
1228 pub subnet_id: RawSubnetId,
1229 pub request_id: u64,
1230 pub http_method: CanisterHttpMethod,
1231 pub url: String,
1232 pub headers: Vec<CanisterHttpHeader>,
1233 #[serde(deserialize_with = "base64::deserialize")]
1234 #[serde(serialize_with = "base64::serialize")]
1235 pub body: Vec<u8>,
1236 pub max_response_bytes: Option<u64>,
1237 pub replication: CanisterHttpReplication,
1238 pub pricing_version: CanisterHttpPricingVersion,
1239}
1240
1241#[derive(Clone, Serialize, Deserialize, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)]
1242pub struct CanisterHttpRequest {
1243 pub subnet_id: Principal,
1244 pub request_id: u64,
1245 pub http_method: CanisterHttpMethod,
1246 pub url: String,
1247 pub headers: Vec<CanisterHttpHeader>,
1248 #[serde(deserialize_with = "base64::deserialize")]
1249 #[serde(serialize_with = "base64::serialize")]
1250 pub body: Vec<u8>,
1251 pub max_response_bytes: Option<u64>,
1252 pub replication: CanisterHttpReplication,
1253 pub pricing_version: CanisterHttpPricingVersion,
1254}
1255
1256impl From<RawCanisterHttpRequest> for CanisterHttpRequest {
1257 fn from(raw_canister_http_request: RawCanisterHttpRequest) -> Self {
1258 Self {
1259 subnet_id: candid::Principal::from_slice(
1260 &raw_canister_http_request.subnet_id.subnet_id,
1261 ),
1262 request_id: raw_canister_http_request.request_id,
1263 http_method: raw_canister_http_request.http_method,
1264 url: raw_canister_http_request.url,
1265 headers: raw_canister_http_request.headers,
1266 body: raw_canister_http_request.body,
1267 max_response_bytes: raw_canister_http_request.max_response_bytes,
1268 replication: raw_canister_http_request.replication,
1269 pricing_version: raw_canister_http_request.pricing_version,
1270 }
1271 }
1272}
1273
1274impl From<CanisterHttpRequest> for RawCanisterHttpRequest {
1275 fn from(canister_http_request: CanisterHttpRequest) -> Self {
1276 Self {
1277 subnet_id: canister_http_request.subnet_id.into(),
1278 request_id: canister_http_request.request_id,
1279 http_method: canister_http_request.http_method,
1280 url: canister_http_request.url,
1281 headers: canister_http_request.headers,
1282 body: canister_http_request.body,
1283 max_response_bytes: canister_http_request.max_response_bytes,
1284 replication: canister_http_request.replication,
1285 pricing_version: canister_http_request.pricing_version,
1286 }
1287 }
1288}
1289
1290#[derive(
1291 Clone, Serialize, Deserialize, Debug, Hash, Eq, PartialEq, Ord, PartialOrd, JsonSchema,
1292)]
1293pub struct CanisterHttpReply {
1294 pub status: u16,
1295 pub headers: Vec<CanisterHttpHeader>,
1296 #[serde(deserialize_with = "base64::deserialize")]
1297 #[serde(serialize_with = "base64::serialize")]
1298 pub body: Vec<u8>,
1299}
1300
1301#[derive(
1302 Clone, Serialize, Deserialize, Debug, Hash, Eq, PartialEq, Ord, PartialOrd, JsonSchema,
1303)]
1304pub struct CanisterHttpReject {
1305 pub reject_code: u64,
1306 pub message: String,
1308}
1309
1310#[derive(
1311 Clone, Serialize, Deserialize, Debug, Hash, Eq, PartialEq, Ord, PartialOrd, JsonSchema,
1312)]
1313pub enum CanisterHttpResponse {
1314 CanisterHttpReply(CanisterHttpReply),
1315 CanisterHttpReject(CanisterHttpReject),
1316}
1317
1318#[derive(Clone, Serialize, Deserialize, Debug, JsonSchema)]
1319pub struct RawMockCanisterHttpResponse {
1320 pub subnet_id: RawSubnetId,
1321 pub request_id: u64,
1322 pub response: CanisterHttpResponse,
1323 pub additional_responses: Vec<CanisterHttpResponse>,
1324}
1325
1326#[derive(Clone, Serialize, Deserialize, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)]
1349pub struct MockCanisterHttpResponse {
1350 pub subnet_id: Principal,
1351 pub request_id: u64,
1352 pub response: CanisterHttpResponse,
1359 pub additional_responses: Vec<CanisterHttpResponse>,
1373}
1374
1375impl From<RawMockCanisterHttpResponse> for MockCanisterHttpResponse {
1376 fn from(raw_mock_canister_http_response: RawMockCanisterHttpResponse) -> Self {
1377 Self {
1378 subnet_id: candid::Principal::from_slice(
1379 &raw_mock_canister_http_response.subnet_id.subnet_id,
1380 ),
1381 request_id: raw_mock_canister_http_response.request_id,
1382 response: raw_mock_canister_http_response.response,
1383 additional_responses: raw_mock_canister_http_response.additional_responses,
1384 }
1385 }
1386}
1387
1388impl From<MockCanisterHttpResponse> for RawMockCanisterHttpResponse {
1389 fn from(mock_canister_http_response: MockCanisterHttpResponse) -> Self {
1390 Self {
1391 subnet_id: RawSubnetId {
1392 subnet_id: mock_canister_http_response.subnet_id.as_slice().to_vec(),
1393 },
1394 request_id: mock_canister_http_response.request_id,
1395 response: mock_canister_http_response.response,
1396 additional_responses: mock_canister_http_response.additional_responses,
1397 }
1398 }
1399}
1400
1401#[derive(Clone, Serialize, Deserialize, Debug, JsonSchema)]
1402pub struct RawMockFlexibleCanisterHttpResponse {
1403 pub subnet_id: RawSubnetId,
1404 pub request_id: u64,
1405 pub responses: Vec<CanisterHttpResponse>,
1406}
1407
1408#[derive(Clone, Serialize, Deserialize, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)]
1416pub struct MockFlexibleCanisterHttpResponse {
1417 pub subnet_id: Principal,
1418 pub request_id: u64,
1419 pub responses: Vec<CanisterHttpResponse>,
1444}
1445
1446impl From<RawMockFlexibleCanisterHttpResponse> for MockFlexibleCanisterHttpResponse {
1447 fn from(raw_mock_flexible_canister_http_response: RawMockFlexibleCanisterHttpResponse) -> Self {
1448 Self {
1449 subnet_id: candid::Principal::from_slice(
1450 &raw_mock_flexible_canister_http_response.subnet_id.subnet_id,
1451 ),
1452 request_id: raw_mock_flexible_canister_http_response.request_id,
1453 responses: raw_mock_flexible_canister_http_response.responses,
1454 }
1455 }
1456}
1457
1458impl From<MockFlexibleCanisterHttpResponse> for RawMockFlexibleCanisterHttpResponse {
1459 fn from(mock_flexible_canister_http_response: MockFlexibleCanisterHttpResponse) -> Self {
1460 Self {
1461 subnet_id: RawSubnetId {
1462 subnet_id: mock_flexible_canister_http_response
1463 .subnet_id
1464 .as_slice()
1465 .to_vec(),
1466 },
1467 request_id: mock_flexible_canister_http_response.request_id,
1468 responses: mock_flexible_canister_http_response.responses,
1469 }
1470 }
1471}
1472
1473#[derive(Clone, Serialize, Deserialize, Debug, JsonSchema)]
1474pub struct RawCanisterSnapshotDownload {
1475 pub sender: RawPrincipalId,
1476 pub canister_id: RawCanisterId,
1477 #[serde(deserialize_with = "base64::deserialize")]
1478 #[serde(serialize_with = "base64::serialize")]
1479 pub snapshot_id: Vec<u8>,
1480 pub snapshot_dir: PathBuf,
1481}
1482
1483#[derive(Clone, Serialize, Deserialize, Debug, JsonSchema)]
1484pub struct RawCanisterSnapshotUpload {
1485 pub sender: RawPrincipalId,
1486 pub canister_id: RawCanisterId,
1487 pub replace_snapshot: Option<RawCanisterSnapshotId>,
1488 pub snapshot_dir: PathBuf,
1489}
1490
1491#[derive(Clone, Serialize, Deserialize, Debug, JsonSchema)]
1492pub struct RawCanisterSnapshotId {
1493 #[serde(deserialize_with = "base64::deserialize")]
1494 #[serde(serialize_with = "base64::serialize")]
1495 pub snapshot_id: Vec<u8>,
1496}