1use crate::{
8 cdk::types::Cycles,
9 config::schema::ComponentChildKind,
10 dto::{
11 fleet_registry::{FleetDirectorySnapshot, FleetRegistryVersion},
12 root_store::RootStoreBootstrapRequest,
13 },
14 ids::{
15 CanisterRole, ComponentBinding, ComponentInstanceId, ComponentSpecId,
16 ComponentTopologyDigest, FleetSubnetRootReleaseSet, ManagedCanisterBinding,
17 },
18};
19use candid::{CandidType, Principal};
20use serde::{Deserialize, Serialize};
21
22#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
29pub struct RootComponentRegistryPreparationRequest {
30 pub store_bootstrap: RootStoreBootstrapRequest,
31 pub expected_fleet_registry: FleetRegistryVersion,
32}
33
34#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
41pub struct RootComponentInitialInventoryStatus {
42 pub fleet_activation_operation_id: [u8; 32],
43 pub component_count: u32,
44 pub inventory_hash: [u8; 32],
45 pub sealed_at_ns: u64,
46 pub directories_converged: bool,
47 pub root_runtime_activated: bool,
48}
49
50#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
57pub struct RootComponentRegistryStatusResponse {
58 pub fleet_subnet_root: Principal,
59 pub prepared_against_registry: FleetRegistryVersion,
60 pub release_set: FleetSubnetRootReleaseSet,
61 pub component_topology_digest: ComponentTopologyDigest,
62 pub next_allocation_sequence: u64,
63 pub reserved_component_instances: u32,
64 pub committed_component_instances: u32,
65 pub managed_descendants: u32,
66 pub known_created_component_canisters: u32,
67 pub encoded_bytes: u64,
68 pub initial_inventory: Option<RootComponentInitialInventoryStatus>,
69}
70
71#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
78pub struct RootComponentAllocationRequest {
79 pub operation_id: [u8; 32],
80 pub component_spec: ComponentSpecId,
81}
82
83#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
90pub struct RootComponentAllocationStatusRequest {
91 pub operation_id: [u8; 32],
92}
93
94#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
101pub struct RootComponentChildAllocationRequest {
102 pub operation_id: [u8; 32],
103 pub component: ComponentInstanceId,
104 pub expected_registry: ComponentRegistryHead,
105 pub child_role: CanisterRole,
106}
107
108#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
115pub struct RootComponentChildAllocationStatusRequest {
116 pub operation_id: [u8; 32],
117 pub component: ComponentInstanceId,
118}
119
120#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
127pub struct RootComponentCreationRequest {
128 pub operation_id: [u8; 32],
129}
130
131#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
138pub struct RootComponentInstallRequest {
139 pub operation_id: [u8; 32],
140}
141
142#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
149pub struct RootComponentCommitRequest {
150 pub operation_id: [u8; 32],
151}
152
153#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
160pub struct RootComponentDirectoryPreparationRequest {
161 pub operation_id: [u8; 32],
162}
163
164#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
171pub struct RootComponentRuntimeActivationRequest {
172 pub operation_id: [u8; 32],
173}
174
175#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
182pub struct RootComponentMembershipActivationRequest {
183 pub operation_id: [u8; 32],
184}
185
186#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
193pub enum ComponentProvisioningOrigin {
194 FleetAdministrator { caller: Principal },
195}
196
197#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
204pub enum RootComponentAllocationPhase {
205 Reserved,
206 CreationIntent,
207 Created,
208 InstallIntent,
209 Installed,
210 Verified,
211 Committed,
212}
213
214#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
221pub enum ComponentLifecycleStatus {
222 Prepared,
223 Active,
224 Draining,
225 Removed,
226}
227
228#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
235pub struct ComponentRegistryHead {
236 pub component: ComponentInstanceId,
237 pub revision: u64,
238 pub content_hash: [u8; 32],
239}
240
241#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
248pub struct ComponentRegistryPartitionRequest {
249 pub component: ComponentInstanceId,
250}
251
252#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
259pub struct ComponentRegistryPartitionResponse {
260 pub head: ComponentRegistryHead,
261 pub binding: ComponentBinding,
262 pub provisioning_origin: ComponentProvisioningOrigin,
263 pub release_set: FleetSubnetRootReleaseSet,
264 pub status: ComponentLifecycleStatus,
265 pub reserved_descendants: u32,
266 pub committed_descendants: u32,
267 pub encoded_bytes: u64,
268}
269
270#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
277pub struct ComponentDirectoryProvenance {
278 pub component: ComponentBinding,
279 pub source_fleet_subnet_root: Principal,
280 pub component_registry_revision: u64,
281 pub component_registry_content_hash: [u8; 32],
282 pub synchronized_at_ns: u64,
283}
284
285#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
292pub struct ComponentDirectoryHead {
293 pub provenance: ComponentDirectoryProvenance,
294 pub descendant_count: u32,
295}
296
297#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
304pub struct ComponentDirectoryHeadRequest {
305 pub component: ComponentInstanceId,
306}
307
308#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
315pub struct ComponentRuntimeDirectoryAuthority {
316 pub fleet: FleetDirectorySnapshot,
317 pub component: ComponentDirectoryHead,
318}
319
320#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
327pub struct ComponentRuntimeDirectoryPreparationRequest {
328 pub operation_id: [u8; 32],
329 pub authority: ComponentRuntimeDirectoryAuthority,
330}
331
332#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
339pub struct ComponentRuntimeDirectorySynchronizationRequest {
340 pub operation_id: [u8; 32],
341 pub authority: ComponentRuntimeDirectoryAuthority,
342}
343
344#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
351pub enum ComponentRuntimePhase {
352 AwaitingDirectory,
353 DirectoryPrepared,
354 Active,
355}
356
357#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
364pub struct ComponentRuntimeActivationEvidence {
365 pub directory_authority_hash: [u8; 32],
366 pub activated_at_ns: u64,
367}
368
369#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
376pub struct ComponentRuntimeActivationRequest {
377 pub operation_id: [u8; 32],
378 pub directory_authority_hash: [u8; 32],
379}
380
381#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
388pub struct ComponentRuntimeStatusResponse {
389 pub operation_id: [u8; 32],
390 pub binding: ManagedCanisterBinding,
391 pub phase: ComponentRuntimePhase,
392 pub authority: Option<ComponentRuntimeDirectoryAuthority>,
393 pub authority_hash: Option<[u8; 32]>,
394 pub activation: Option<ComponentRuntimeActivationEvidence>,
395}
396
397#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
404pub struct RootComponentCreationEvidence {
405 pub wasm_store: Principal,
406 pub payload_hash: [u8; 32],
407 pub payload_size_bytes: u64,
408 pub initial_cycles: Cycles,
409 pub controller: Principal,
410 pub canister: Option<Principal>,
411}
412
413#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
420pub struct RootComponentInstallEvidence {
421 pub raw_module_hash: [u8; 32],
422 pub chunk_hashes: Vec<Vec<u8>>,
423 pub binding: ComponentBinding,
424}
425
426#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
433pub struct RootComponentAllocationResponse {
434 pub operation_id: [u8; 32],
435 pub allocation_sequence: u64,
436 pub component: ComponentInstanceId,
437 pub component_spec: ComponentSpecId,
438 pub spec_hash: [u8; 32],
439 pub role: CanisterRole,
440 pub provisioning_origin: ComponentProvisioningOrigin,
441 pub release_set: FleetSubnetRootReleaseSet,
442 pub phase: RootComponentAllocationPhase,
443 pub creation: Option<RootComponentCreationEvidence>,
444 pub installation: Option<RootComponentInstallEvidence>,
445}
446
447#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
454pub struct RootComponentChildAllocationResponse {
455 pub operation_id: [u8; 32],
456 pub component: ComponentInstanceId,
457 pub parent_canister_id: Principal,
458 pub parent_role: CanisterRole,
459 pub child_role: CanisterRole,
460 pub child_kind: ComponentChildKind,
461 pub maximum_instances_per_parent: u32,
462 pub maximum_descendants: u32,
463 pub maximum_registry_bytes: u64,
464 pub reserved_against_registry: ComponentRegistryHead,
465 pub release_set: FleetSubnetRootReleaseSet,
466}
467
468#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
475pub struct RootComponentCommitResponse {
476 pub allocation: RootComponentAllocationResponse,
477 pub registry: ComponentRegistryPartitionResponse,
478 pub directory: ComponentDirectoryHead,
479}
480
481#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
488pub struct RootComponentDirectoryPreparationResponse {
489 pub committed: RootComponentCommitResponse,
490 pub target: ComponentRuntimeStatusResponse,
491}
492
493#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
500pub struct RootComponentRuntimeActivationResponse {
501 pub committed: RootComponentCommitResponse,
502 pub target: ComponentRuntimeStatusResponse,
503}
504
505#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
512pub struct RootComponentMembershipActivationResponse {
513 pub allocation: RootComponentAllocationResponse,
514 pub registry: ComponentRegistryPartitionResponse,
515 pub directory: ComponentDirectoryHead,
516 pub target: ComponentRuntimeStatusResponse,
517}
518
519#[cfg(test)]
520mod tests {
521 use super::*;
522 use crate::{
523 dto::root_store::RootStoreBootstrapRequest,
524 ids::{
525 AppId, CanonicalNetworkId, FleetCoordinatorBinding, FleetId, FleetKey,
526 FleetRegistryAuthority, ReleaseBuildId, ReleaseBuildNonce, ReleaseSetDigest, SubnetId,
527 },
528 };
529
530 #[test]
531 fn component_registry_contracts_round_trip_through_candid() {
532 let request = RootComponentRegistryPreparationRequest {
533 store_bootstrap: RootStoreBootstrapRequest {
534 manifest_payload_size_bytes: 128,
535 },
536 expected_fleet_registry: FleetRegistryVersion {
537 authority: fleet_registry_authority(),
538 revision: 4,
539 content_hash: [5; 32],
540 },
541 };
542 let response = RootComponentRegistryStatusResponse {
543 fleet_subnet_root: Principal::from_slice(&[6; 29]),
544 prepared_against_registry: request.expected_fleet_registry.clone(),
545 release_set: FleetSubnetRootReleaseSet {
546 release_build_id: ReleaseBuildId::from_nonce(ReleaseBuildNonce::from_random_bytes(
547 [7; 32],
548 )),
549 manifest_digest: ReleaseSetDigest::from_bytes([8; 32]),
550 },
551 component_topology_digest: ComponentTopologyDigest::from_bytes([9; 32]),
552 next_allocation_sequence: 1,
553 reserved_component_instances: 0,
554 committed_component_instances: 0,
555 managed_descendants: 0,
556 known_created_component_canisters: 0,
557 encoded_bytes: 0,
558 initial_inventory: Some(RootComponentInitialInventoryStatus {
559 fleet_activation_operation_id: [10; 32],
560 component_count: 0,
561 inventory_hash: [11; 32],
562 sealed_at_ns: 12,
563 directories_converged: true,
564 root_runtime_activated: true,
565 }),
566 };
567 let allocation = RootComponentAllocationResponse {
568 operation_id: [10; 32],
569 allocation_sequence: 1,
570 component: ComponentInstanceId::from_generated_bytes([11; 32]),
571 component_spec: "projects".parse().expect("Component Spec ID"),
572 spec_hash: [12; 32],
573 role: CanisterRole::new("project_hub"),
574 provisioning_origin: ComponentProvisioningOrigin::FleetAdministrator {
575 caller: Principal::from_slice(&[13; 29]),
576 },
577 release_set: response.release_set,
578 phase: RootComponentAllocationPhase::Reserved,
579 creation: None,
580 installation: None,
581 };
582 let created = RootComponentAllocationResponse {
583 phase: RootComponentAllocationPhase::Created,
584 creation: Some(RootComponentCreationEvidence {
585 wasm_store: Principal::from_slice(&[14; 29]),
586 payload_hash: [15; 32],
587 payload_size_bytes: 4_096,
588 initial_cycles: Cycles::new(5_000_000_000_000),
589 controller: Principal::from_slice(&[6; 29]),
590 canister: Some(Principal::from_slice(&[16; 29])),
591 }),
592 installation: None,
593 ..allocation.clone()
594 };
595 let request_bytes = candid::encode_one(&request).expect("encode request");
596 let response_bytes = candid::encode_one(&response).expect("encode response");
597 let allocation_bytes = candid::encode_one(&allocation).expect("encode allocation");
598 let created_bytes = candid::encode_one(&created).expect("encode created allocation");
599
600 assert_eq!(
601 candid::decode_one::<RootComponentRegistryPreparationRequest>(&request_bytes)
602 .expect("decode request"),
603 request
604 );
605 assert_eq!(
606 candid::decode_one::<RootComponentRegistryStatusResponse>(&response_bytes)
607 .expect("decode response"),
608 response
609 );
610 assert_eq!(
611 candid::decode_one::<RootComponentAllocationResponse>(&allocation_bytes)
612 .expect("decode allocation"),
613 allocation
614 );
615 assert_eq!(
616 candid::decode_one::<RootComponentAllocationResponse>(&created_bytes)
617 .expect("decode created allocation"),
618 created
619 );
620 }
621
622 #[test]
623 fn component_commit_response_round_trips_through_candid() {
624 let root = Principal::from_slice(&[6; 29]);
625 let component = ComponentInstanceId::from_generated_bytes([11; 32]);
626 let component_spec: ComponentSpecId = "projects".parse().expect("Component Spec ID");
627 let release_set = FleetSubnetRootReleaseSet {
628 release_build_id: ReleaseBuildId::from_nonce(ReleaseBuildNonce::from_random_bytes(
629 [7; 32],
630 )),
631 manifest_digest: ReleaseSetDigest::from_bytes([8; 32]),
632 };
633 let provisioning_origin = ComponentProvisioningOrigin::FleetAdministrator {
634 caller: Principal::from_slice(&[13; 29]),
635 };
636 let binding = ComponentBinding {
637 authority: fleet_registry_authority(),
638 component,
639 component_spec: component_spec.clone(),
640 spec_hash: [12; 32],
641 role: CanisterRole::new("project_hub"),
642 placement_subnet: SubnetId::from_principal(Principal::from_slice(&[17; 29])),
643 fleet_subnet_root: root,
644 canister_id: Principal::from_slice(&[16; 29]),
645 };
646 let head = ComponentRegistryHead {
647 component,
648 revision: 1,
649 content_hash: [18; 32],
650 };
651 let committed = RootComponentCommitResponse {
652 allocation: RootComponentAllocationResponse {
653 operation_id: [10; 32],
654 allocation_sequence: 1,
655 component,
656 component_spec,
657 spec_hash: binding.spec_hash,
658 role: binding.role.clone(),
659 provisioning_origin: provisioning_origin.clone(),
660 release_set,
661 phase: RootComponentAllocationPhase::Committed,
662 creation: Some(RootComponentCreationEvidence {
663 wasm_store: Principal::from_slice(&[14; 29]),
664 payload_hash: [15; 32],
665 payload_size_bytes: 4_096,
666 initial_cycles: Cycles::new(5_000_000_000_000),
667 controller: root,
668 canister: Some(binding.canister_id),
669 }),
670 installation: Some(RootComponentInstallEvidence {
671 raw_module_hash: [20; 32],
672 chunk_hashes: vec![vec![21; 32]],
673 binding: binding.clone(),
674 }),
675 },
676 registry: ComponentRegistryPartitionResponse {
677 head: head.clone(),
678 binding: binding.clone(),
679 provisioning_origin,
680 release_set,
681 status: ComponentLifecycleStatus::Prepared,
682 reserved_descendants: 0,
683 committed_descendants: 0,
684 encoded_bytes: 2_048,
685 },
686 directory: ComponentDirectoryHead {
687 provenance: ComponentDirectoryProvenance {
688 component: binding,
689 source_fleet_subnet_root: root,
690 component_registry_revision: head.revision,
691 component_registry_content_hash: head.content_hash,
692 synchronized_at_ns: 19,
693 },
694 descendant_count: 0,
695 },
696 };
697 let committed_bytes = candid::encode_one(&committed).expect("encode committed allocation");
698
699 assert_eq!(
700 candid::decode_one::<RootComponentCommitResponse>(&committed_bytes)
701 .expect("decode committed allocation"),
702 committed
703 );
704 }
705
706 fn fleet_registry_authority() -> FleetRegistryAuthority {
707 FleetRegistryAuthority {
708 binding: FleetCoordinatorBinding {
709 fleet: crate::ids::FleetBinding {
710 fleet: FleetKey {
711 canonical_network_id: CanonicalNetworkId::public_ic(),
712 fleet_id: FleetId::from_generated_bytes([1; 32]),
713 },
714 app: AppId::from("toko"),
715 },
716 coordinator_subnet: SubnetId::from_principal(Principal::from_slice(&[2; 29])),
717 coordinator: Principal::from_slice(&[3; 29]),
718 },
719 epoch: 1,
720 }
721 }
722
723 #[test]
724 fn component_creation_request_round_trips_through_candid() {
725 let request = RootComponentCreationRequest {
726 operation_id: [10; 32],
727 };
728 let bytes = candid::encode_one(request).expect("encode creation request");
729
730 assert_eq!(
731 candid::decode_one::<RootComponentCreationRequest>(&bytes)
732 .expect("decode creation request"),
733 request
734 );
735 }
736
737 #[test]
738 fn component_child_reservation_contracts_round_trip_through_candid() {
739 let component = ComponentInstanceId::from_generated_bytes([11; 32]);
740 let registry = ComponentRegistryHead {
741 component,
742 revision: 2,
743 content_hash: [12; 32],
744 };
745 let request = RootComponentChildAllocationRequest {
746 operation_id: [13; 32],
747 component,
748 expected_registry: registry.clone(),
749 child_role: CanisterRole::new("project_instance"),
750 };
751 let status_request = RootComponentChildAllocationStatusRequest {
752 operation_id: request.operation_id,
753 component,
754 };
755 let response = RootComponentChildAllocationResponse {
756 operation_id: request.operation_id,
757 component,
758 parent_canister_id: Principal::from_slice(&[14; 29]),
759 parent_role: CanisterRole::new("project_hub"),
760 child_role: request.child_role.clone(),
761 child_kind: ComponentChildKind::Instance,
762 maximum_instances_per_parent: 10_000,
763 maximum_descendants: 20_000,
764 maximum_registry_bytes: 16_777_216,
765 reserved_against_registry: registry,
766 release_set: FleetSubnetRootReleaseSet {
767 release_build_id: ReleaseBuildId::from_nonce(ReleaseBuildNonce::from_random_bytes(
768 [15; 32],
769 )),
770 manifest_digest: ReleaseSetDigest::from_bytes([16; 32]),
771 },
772 };
773
774 let request_bytes = candid::encode_one(&request).expect("encode child reservation");
775 let status_bytes =
776 candid::encode_one(status_request).expect("encode child reservation status");
777 let response_bytes = candid::encode_one(&response).expect("encode child response");
778
779 assert_eq!(
780 candid::decode_one::<RootComponentChildAllocationRequest>(&request_bytes)
781 .expect("decode child reservation"),
782 request
783 );
784 assert_eq!(
785 candid::decode_one::<RootComponentChildAllocationStatusRequest>(&status_bytes)
786 .expect("decode child reservation status"),
787 status_request
788 );
789 assert_eq!(
790 candid::decode_one::<RootComponentChildAllocationResponse>(&response_bytes)
791 .expect("decode child response"),
792 response
793 );
794 }
795
796 #[test]
797 fn component_install_request_round_trips_through_candid() {
798 let request = RootComponentInstallRequest {
799 operation_id: [10; 32],
800 };
801 let bytes = candid::encode_one(request).expect("encode install request");
802
803 assert_eq!(
804 candid::decode_one::<RootComponentInstallRequest>(&bytes)
805 .expect("decode install request"),
806 request
807 );
808 }
809
810 #[test]
811 fn component_commit_request_round_trips_through_candid() {
812 let request = RootComponentCommitRequest {
813 operation_id: [10; 32],
814 };
815 let bytes = candid::encode_one(request).expect("encode commit request");
816
817 assert_eq!(
818 candid::decode_one::<RootComponentCommitRequest>(&bytes)
819 .expect("decode commit request"),
820 request
821 );
822 }
823
824 #[test]
825 fn component_runtime_activation_requests_round_trip_through_candid() {
826 let root_request = RootComponentRuntimeActivationRequest {
827 operation_id: [22; 32],
828 };
829 let target_request = ComponentRuntimeActivationRequest {
830 operation_id: root_request.operation_id,
831 directory_authority_hash: [23; 32],
832 };
833 let membership_request = RootComponentMembershipActivationRequest {
834 operation_id: root_request.operation_id,
835 };
836 let root_bytes = candid::encode_one(root_request).expect("encode root activation request");
837 let target_bytes =
838 candid::encode_one(target_request).expect("encode target activation request");
839 let membership_bytes =
840 candid::encode_one(membership_request).expect("encode membership activation request");
841
842 assert_eq!(
843 candid::decode_one::<RootComponentRuntimeActivationRequest>(&root_bytes)
844 .expect("decode root activation request"),
845 root_request
846 );
847 assert_eq!(
848 candid::decode_one::<ComponentRuntimeActivationRequest>(&target_bytes)
849 .expect("decode target activation request"),
850 target_request
851 );
852 assert_eq!(
853 candid::decode_one::<RootComponentMembershipActivationRequest>(&membership_bytes)
854 .expect("decode membership activation request"),
855 membership_request
856 );
857 }
858}