1use 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#[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 #[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 #[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
391pub 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#[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#[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
471impl 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#[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#[derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize, Default, JsonSchema)]
589pub struct IcpConfig {
590 pub beta_features: Option<IcpConfigFlag>,
592 pub function_name_length_limits: Option<IcpConfigFlag>,
594 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]
603 DefaultConfig,
604}
605
606#[derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize, Default, JsonSchema)]
615pub struct IcpFeatures {
616 pub registry: Option<IcpFeaturesConfig>,
619 pub cycles_minting: Option<IcpFeaturesConfig>,
623 pub icp_token: Option<IcpFeaturesConfig>,
626 pub cycles_token: Option<IcpFeaturesConfig>,
629 pub nns_governance: Option<IcpFeaturesConfig>,
633 pub sns: Option<IcpFeaturesConfig>,
637 pub ii: Option<IcpFeaturesConfig>,
640 pub nns_ui: Option<IcpFeaturesConfig>,
644 pub bitcoin: Option<IcpFeaturesConfig>,
647 pub dogecoin: Option<IcpFeaturesConfig>,
650 pub canister_migration: Option<IcpFeaturesConfig>,
653}
654
655#[derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize, JsonSchema)]
656pub enum InitialTime {
657 Timestamp(RawTime),
661 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#[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#[derive(
783 Debug, Clone, Eq, Hash, PartialEq, Ord, PartialOrd, Serialize, Deserialize, JsonSchema,
784)]
785pub enum SubnetInstructionConfig {
786 Production,
788 Benchmarking,
790}
791
792#[derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize, JsonSchema)]
795pub enum SubnetStateConfig {
796 New,
798 FromPath(PathBuf),
801 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 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 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 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 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 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 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 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 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 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 if sns.is_some() {
959 check_empty_subnet(&self.sns, "SNS", "sns")?;
960 self.sns = Some(self.sns.unwrap_or_default());
961 }
962 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#[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#[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 pub instruction_config: SubnetInstructionConfig,
1004 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#[derive(
1132 Clone, Serialize, Deserialize, Debug, Hash, Eq, PartialEq, Ord, PartialOrd, JsonSchema,
1133)]
1134pub enum CanisterHttpReplication {
1135 FullyReplicated,
1141 NonReplicated,
1149 Flexible {
1154 total_requests: u32,
1156 min_responses: u32,
1158 max_responses: u32,
1160 },
1161}
1162
1163#[derive(
1165 Clone, Serialize, Deserialize, Debug, Hash, Eq, PartialEq, Ord, PartialOrd, JsonSchema,
1166)]
1167pub enum CanisterHttpPricingVersion {
1168 Legacy,
1171 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 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#[derive(Clone, Serialize, Deserialize, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)]
1300pub struct MockCanisterHttpResponse {
1301 pub subnet_id: Principal,
1302 pub request_id: u64,
1303 pub response: CanisterHttpResponse,
1310 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#[derive(Clone, Serialize, Deserialize, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)]
1367pub struct MockFlexibleCanisterHttpResponse {
1368 pub subnet_id: Principal,
1369 pub request_id: u64,
1370 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}