From de542231e1b3ff70813fb69f93654b031a90b426 Mon Sep 17 00:00:00 2001 From: rupansh Date: Sun, 3 Nov 2024 05:51:24 +0530 Subject: [PATCH] feat: add proof of participation merkleization --- Cargo.lock | 199 +++-- src/canister/individual_user_template/can.did | 7 +- .../api/canister_lifecycle/post_upgrade.rs | 13 +- .../deregister_subnet_orchestrator.rs | 10 +- .../get_all_subnet_orchestrators.rs | 2 +- .../canister_management/known_principal.rs | 2 +- ...populate_known_principal_for_all_subnet.rs | 2 +- .../provision_subnet_orchestrator.rs | 15 +- .../register_new_subnet_orhestrator.rs | 15 +- ..._upgrades_for_individual_user_canisters.rs | 2 +- .../update_canisters_last_access_time.rs | 2 +- ...date_profile_owner_for_individual_users.rs | 2 +- .../update_timers_for_hon_game.rs | 2 +- .../upgrade_canisters_in_network.rs | 8 +- .../upgrade_specific_individual_canister.rs | 2 +- ...aiming_cycles_from_individual_canisters.rs | 2 +- ...ycles_from_subnet_orchestrator_canister.rs | 2 +- .../src/data_model/mod.rs | 59 +- .../utils/registered_subnet_orchestrator.rs | 2 +- src/canister/user_index/can.did | 7 +- .../create_pool_of_available_canisters.rs | 81 +- .../get_subnet_backup_capacity.rs | 3 +- ...upgrade_user_canisters_with_latest_wasm.rs | 9 +- ...dividual_user_canister_with_latest_wasm.rs | 9 +- ...ot_exists_and_optionally_allow_referrer.rs | 12 +- .../api/user_signup/are_signups_enabled.rs | 8 +- src/canister/user_index/src/data_model/mod.rs | 46 +- .../src/util/canister_management.rs | 17 +- .../util/types/individual_user_canister.rs | 6 +- src/lib/shared_utils/Cargo.toml | 11 +- .../participant_crypto/merkle/backing.rs | 113 +++ .../participant_crypto/merkle/layout.rs | 708 ++++++++++++++++++ .../common/participant_crypto/merkle/mod.rs | 103 +++ .../src/common/participant_crypto/mod.rs | 107 ++- 34 files changed, 1399 insertions(+), 189 deletions(-) create mode 100644 src/lib/shared_utils/src/common/participant_crypto/merkle/backing.rs create mode 100644 src/lib/shared_utils/src/common/participant_crypto/merkle/layout.rs create mode 100644 src/lib/shared_utils/src/common/participant_crypto/merkle/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 432176c3..da16a733 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -132,6 +132,12 @@ version = "1.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10f00e1f6e58a40e807377c75c6a7f97bf9044fab57816f2414e6f5f4499d7b8" +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + [[package]] name = "arrayvec" version = "0.5.2" @@ -168,7 +174,7 @@ checksum = "965c2d33e53cb6b267e148a4cb0760bc01f4904c1cd4bb4002a085bb016d1490" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", "synstructure", ] @@ -180,7 +186,7 @@ checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -191,7 +197,7 @@ checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -318,6 +324,19 @@ dependencies = [ "wyz", ] +[[package]] +name = "blake3" +version = "1.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8ee0c1824c4dea5b5f81736aff91bae041d2c07ee1192bec91054e10e3e601e" +dependencies = [ + "arrayref", + "arrayvec 0.7.6", + "cc", + "cfg-if", + "constant_time_eq", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -347,7 +366,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", "syn_derive", ] @@ -422,6 +441,12 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64fa3c856b712db6612c019f14756e64e4bcea13337a6b33b696333a9eaa2d06" +[[package]] +name = "byte-slice-cast" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" + [[package]] name = "byte-unit" version = "4.0.19" @@ -525,7 +550,7 @@ dependencies = [ "lazy_static", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -672,7 +697,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -738,6 +763,12 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +[[package]] +name = "constant_time_eq" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" + [[package]] name = "convert_case" version = "0.4.0" @@ -895,7 +926,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -984,7 +1015,7 @@ dependencies = [ "proc-macro2", "quote", "strsim 0.11.1", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -1006,7 +1037,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core 0.20.10", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -1074,7 +1105,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -1158,7 +1189,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -1197,6 +1228,12 @@ dependencies = [ "signature", ] +[[package]] +name = "ed25519-compact" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9b3460f44bea8cd47f45a0c70892f1eff856d97cd55358b2f73f663789f6190" + [[package]] name = "ed25519-dalek" version = "2.1.1" @@ -1417,7 +1454,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -1541,6 +1578,21 @@ dependencies = [ "crunchy", ] +[[package]] +name = "hash-db" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e7d7786361d7425ae2fe4f9e407eb0efaa0840f5212d109cc018c40c35c6ab4" + +[[package]] +name = "hash256-std-hasher" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92c171d55b98633f4ed3860808f004099b36c1cc29c42cfc53aa8591b21efcf2" +dependencies = [ + "crunchy", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -1947,7 +1999,7 @@ dependencies = [ "quote", "serde", "serde_tokenstream 0.2.2", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -1961,7 +2013,7 @@ dependencies = [ "quote", "serde", "serde_tokenstream 0.2.2", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -3604,7 +3656,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -3650,6 +3702,17 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "impl-trait-for-tuples" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.89", +] + [[package]] name = "indexmap" version = "1.9.3" @@ -3965,6 +4028,15 @@ dependencies = [ "libc", ] +[[package]] +name = "memory-db" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "808b50db46293432a45e63bc15ea51e0ab4c0a1647b8eb114e31a3e698dd6fbe" +dependencies = [ + "hash-db", +] + [[package]] name = "merlin" version = "3.0.0" @@ -4043,7 +4115,7 @@ dependencies = [ "cfg-if", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -4225,6 +4297,18 @@ dependencies = [ "group", ] +[[package]] +name = "parity-scale-codec" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be4817d39f3272f69c59fe05d0535ae6456c2dc2fa1ba02910296c7e0a5c590" +dependencies = [ + "arrayvec 0.7.6", + "byte-slice-cast", + "impl-trait-for-tuples", + "rustversion", +] + [[package]] name = "parking_lot" version = "0.12.3" @@ -4309,7 +4393,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -4361,7 +4445,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -4538,7 +4622,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479cf940fbbb3426c32c5d5176f62ad57549a0bb84773423ba8be9d089f5faba" dependencies = [ "proc-macro2", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -4601,9 +4685,9 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" [[package]] name = "proc-macro2" -version = "1.0.89" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] @@ -4647,7 +4731,7 @@ dependencies = [ "prost", "prost-types", "regex", - "syn 2.0.77", + "syn 2.0.89", "tempfile", ] @@ -4661,7 +4745,7 @@ dependencies = [ "itertools", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -5217,7 +5301,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -5320,7 +5404,7 @@ checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -5331,7 +5415,7 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -5364,7 +5448,7 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -5387,7 +5471,7 @@ dependencies = [ "proc-macro2", "quote", "serde", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -5474,20 +5558,27 @@ dependencies = [ name = "shared_utils" version = "0.1.0" dependencies = [ + "blake3", "candid", "ciborium", - "ed25519-dalek", + "ed25519-compact", "futures", + "hash-db", + "hash256-std-hasher", "ic-cdk 0.15.1", "ic-cdk-timers", "ic-stable-structures", "icrc-ledger-types 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "memory-db", + "parity-scale-codec", "pprof", "rmp-serde", "serde", "serde_bytes", "serde_json_any_key", "test_utils", + "trie-db", + "trie-root", ] [[package]] @@ -5648,7 +5739,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -5693,9 +5784,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.77" +version = "2.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" +checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e" dependencies = [ "proc-macro2", "quote", @@ -5711,7 +5802,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -5731,7 +5822,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -5803,7 +5894,7 @@ checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -5904,7 +5995,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -6018,7 +6109,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -6073,6 +6164,26 @@ dependencies = [ "tracing-serde", ] +[[package]] +name = "trie-db" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c992b4f40c234a074d48a757efeabb1a6be88af84c0c23f7ca158950cb0ae7f" +dependencies = [ + "hash-db", + "log", + "smallvec", +] + +[[package]] +name = "trie-root" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4ed310ef5ab98f5fa467900ed906cb9232dd5376597e00fd4cba2a449d06c0b" +dependencies = [ + "hash-db", +] + [[package]] name = "try-lock" version = "0.2.5" @@ -6284,7 +6395,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", "wasm-bindgen-shared", ] @@ -6318,7 +6429,7 @@ checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -6624,7 +6735,7 @@ checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", "synstructure", ] @@ -6646,7 +6757,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -6666,7 +6777,7 @@ checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", "synstructure", ] @@ -6687,7 +6798,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -6709,5 +6820,5 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] diff --git a/src/canister/individual_user_template/can.did b/src/canister/individual_user_template/can.did index 88dedfc6..dac5b6ca 100644 --- a/src/canister/individual_user_template/can.did +++ b/src/canister/individual_user_template/can.did @@ -348,7 +348,12 @@ type PostViewStatistics = record { threshold_view_count : nat64; }; type Principals = record { principals : vec principal }; -type ProofOfChild = record { "principal" : principal; signature : blob }; +type ProofOfChild = record { + proof_of_inclusion : vec blob; + "principal" : principal; + children_proof : ProofOfChildren; +}; +type ProofOfChildren = record { signature : blob; merkle_root : blob }; type ProofOfParticipation = record { chain : vec ProofOfChild }; type RejectionCode = variant { NoError; diff --git a/src/canister/platform_orchestrator/src/api/canister_lifecycle/post_upgrade.rs b/src/canister/platform_orchestrator/src/api/canister_lifecycle/post_upgrade.rs index 804c1302..bff21a44 100644 --- a/src/canister/platform_orchestrator/src/api/canister_lifecycle/post_upgrade.rs +++ b/src/canister/platform_orchestrator/src/api/canister_lifecycle/post_upgrade.rs @@ -3,7 +3,7 @@ use ic_cdk::api::call::ArgDecoderConfig; use ic_cdk_macros::post_upgrade; use ic_stable_structures::Memory; use shared_utils::{ - canister_specific::platform_orchestrator::types::args::PlatformOrchestratorInitArgs, + canister_specific::{platform_orchestrator::types::args::PlatformOrchestratorInitArgs, post_cache}, common::utils::system_time, }; @@ -13,6 +13,7 @@ use crate::{data_model::memory, CANISTER_DATA}; pub fn post_upgrade() { restore_data_from_stable_memory(); update_version_from_args(); + initialize_children_merkle(); } fn restore_data_from_stable_memory() { @@ -38,3 +39,13 @@ fn update_version_from_args() { canister_data.version_detail.last_update_on = system_time::get_current_system_time(); }) } + +// TODO: remove this once the upgrade is complete +fn initialize_children_merkle() { + CANISTER_DATA.with_borrow_mut(|cdata| { + let mut children: Vec<_> = cdata.subnet_orchestrators().iter().copied().collect(); + children.extend(cdata.post_cache_orchestrators()); + + cdata.children_merkle.insert_children(children) + }); +} diff --git a/src/canister/platform_orchestrator/src/api/canister_management/deregister_subnet_orchestrator.rs b/src/canister/platform_orchestrator/src/api/canister_management/deregister_subnet_orchestrator.rs index a9b7343a..c10b2f87 100644 --- a/src/canister/platform_orchestrator/src/api/canister_management/deregister_subnet_orchestrator.rs +++ b/src/canister/platform_orchestrator/src/api/canister_management/deregister_subnet_orchestrator.rs @@ -6,14 +6,6 @@ use crate::{guard::is_caller::is_caller_global_admin_or_controller, CANISTER_DAT #[update(guard = "is_caller_global_admin_or_controller")] fn deregister_subnet_orchestrator(canister_id: Principal, remove_it_completely: bool) { CANISTER_DATA.with_borrow_mut(|canister_data| { - canister_data - .subet_orchestrator_with_capacity_left - .remove(&canister_id); - - if remove_it_completely { - canister_data - .all_subnet_orchestrator_canisters_list - .remove(&canister_id); - } + canister_data.remove_subnet_orchestrator(canister_id, remove_it_completely); }); } diff --git a/src/canister/platform_orchestrator/src/api/canister_management/get_all_subnet_orchestrators.rs b/src/canister/platform_orchestrator/src/api/canister_management/get_all_subnet_orchestrators.rs index 0b1a61fd..d289022d 100644 --- a/src/canister/platform_orchestrator/src/api/canister_management/get_all_subnet_orchestrators.rs +++ b/src/canister/platform_orchestrator/src/api/canister_management/get_all_subnet_orchestrators.rs @@ -8,7 +8,7 @@ use crate::CANISTER_DATA; #[query] fn get_all_subnet_orchestrators() -> Vec { CANISTER_DATA.with_borrow(|canister_data| { - let canisters = canister_data.all_subnet_orchestrator_canisters_list.iter().map(|canister_id| {*canister_id}).collect::>(); + let canisters = canister_data.subnet_orchestrators().iter().map(|canister_id| {*canister_id}).collect::>(); canisters }) } \ No newline at end of file diff --git a/src/canister/platform_orchestrator/src/api/canister_management/known_principal.rs b/src/canister/platform_orchestrator/src/api/canister_management/known_principal.rs index f055dd95..f5d7a0f5 100644 --- a/src/canister/platform_orchestrator/src/api/canister_management/known_principal.rs +++ b/src/canister/platform_orchestrator/src/api/canister_management/known_principal.rs @@ -75,7 +75,7 @@ async fn issue_update_known_principal_for_all_subnet( ) { let subnet_list: Vec = CANISTER_DATA.with_borrow(|canister_data| { canister_data - .all_subnet_orchestrator_canisters_list + .subnet_orchestrators() .iter() .copied() .collect() diff --git a/src/canister/platform_orchestrator/src/api/canister_management/populate_known_principal_for_all_subnet.rs b/src/canister/platform_orchestrator/src/api/canister_management/populate_known_principal_for_all_subnet.rs index 4646a511..b1c41f59 100644 --- a/src/canister/platform_orchestrator/src/api/canister_management/populate_known_principal_for_all_subnet.rs +++ b/src/canister/platform_orchestrator/src/api/canister_management/populate_known_principal_for_all_subnet.rs @@ -10,7 +10,7 @@ use crate::{guard::is_caller::is_caller_global_admin_or_controller, CANISTER_DAT #[update(guard = "is_caller_global_admin_or_controller")] async fn populate_known_principal_for_all_subnet() { let subnet_orchestrators: Vec = CANISTER_DATA.with_borrow(|canister_data| { - canister_data.all_subnet_orchestrator_canisters_list.iter().copied().collect() + canister_data.subnet_orchestrators().iter().copied().collect() }); for subnet_id in subnet_orchestrators { diff --git a/src/canister/platform_orchestrator/src/api/canister_management/provision_subnet_orchestrator.rs b/src/canister/platform_orchestrator/src/api/canister_management/provision_subnet_orchestrator.rs index f7f9f4e2..b1cc86f7 100644 --- a/src/canister/platform_orchestrator/src/api/canister_management/provision_subnet_orchestrator.rs +++ b/src/canister/platform_orchestrator/src/api/canister_management/provision_subnet_orchestrator.rs @@ -105,19 +105,14 @@ pub async fn provision_subnet_orchestrator_canister( ); CANISTER_DATA.with_borrow_mut(|canister_data| { - canister_data - .all_post_cache_orchestrator_list - .insert(post_cache_canister_id); - canister_data - .all_subnet_orchestrator_canisters_list - .insert(subnet_orchestrator_canister_id); - canister_data - .subet_orchestrator_with_capacity_left - .insert(subnet_orchestrator_canister_id); + canister_data.insert_subnet_orchestrator_and_post_cache( + subnet_orchestrator_canister_id, + post_cache_canister_id + ); }); let mut proof_of_participation = ProofOfParticipation::new_for_root(); - proof_of_participation = proof_of_participation.derive_for_child(subnet_orchestrator_canister_id).await.unwrap(); + proof_of_participation = proof_of_participation.derive_for_child(&CANISTER_DATA, subnet_orchestrator_canister_id).await.unwrap(); let user_index_init_arg = UserIndexInitArgs { known_principal_ids: Some(known_principal_map.clone()), access_control_map: None, diff --git a/src/canister/platform_orchestrator/src/api/canister_management/register_new_subnet_orhestrator.rs b/src/canister/platform_orchestrator/src/api/canister_management/register_new_subnet_orhestrator.rs index 3bad50e9..37e5708c 100644 --- a/src/canister/platform_orchestrator/src/api/canister_management/register_new_subnet_orhestrator.rs +++ b/src/canister/platform_orchestrator/src/api/canister_management/register_new_subnet_orhestrator.rs @@ -33,7 +33,7 @@ async fn register_new_subnet_orchestrator( if let Some(first_subnet_orchestrator) = CANISTER_DATA.with_borrow(|canister_data| { canister_data - .all_subnet_orchestrator_canisters_list + .subnet_orchestrators() .iter() .next() .copied() @@ -59,15 +59,10 @@ async fn register_new_subnet_orchestrator( } CANISTER_DATA.with_borrow_mut(|canister_data| { - canister_data - .all_subnet_orchestrator_canisters_list - .insert(new_subnet_orchestrator_caniter_id); - - if subnet_is_available_for_provisioning_individual_canister { - canister_data - .subet_orchestrator_with_capacity_left - .insert(new_subnet_orchestrator_caniter_id); - } + canister_data.insert_subnet_orchestrator( + new_subnet_orchestrator_caniter_id, + subnet_is_available_for_provisioning_individual_canister, + ); Ok(()) }) } diff --git a/src/canister/platform_orchestrator/src/api/canister_management/stop_upgrades_for_individual_user_canisters.rs b/src/canister/platform_orchestrator/src/api/canister_management/stop_upgrades_for_individual_user_canisters.rs index 9aed5630..bb2c7643 100644 --- a/src/canister/platform_orchestrator/src/api/canister_management/stop_upgrades_for_individual_user_canisters.rs +++ b/src/canister/platform_orchestrator/src/api/canister_management/stop_upgrades_for_individual_user_canisters.rs @@ -7,7 +7,7 @@ use crate::{guard::is_caller::is_caller_global_admin_or_controller, CANISTER_DAT async fn stop_upgrades_for_individual_user_canisters() -> Result { let subnet_orchestrator_list = CANISTER_DATA.with_borrow(|canister_data| { - canister_data.all_subnet_orchestrator_canisters_list.clone() + canister_data.subnet_orchestrators().clone() }); for subnet_orchestrator in subnet_orchestrator_list { diff --git a/src/canister/platform_orchestrator/src/api/canister_management/update_canisters_last_access_time.rs b/src/canister/platform_orchestrator/src/api/canister_management/update_canisters_last_access_time.rs index 6e9ed630..22c4ec9a 100644 --- a/src/canister/platform_orchestrator/src/api/canister_management/update_canisters_last_access_time.rs +++ b/src/canister/platform_orchestrator/src/api/canister_management/update_canisters_last_access_time.rs @@ -6,7 +6,7 @@ use crate::{guard::is_caller::is_caller_global_admin_or_controller, CANISTER_DAT #[update(guard = "is_caller_global_admin_or_controller")] async fn update_canisters_last_functionality_access_time() -> Result { let subnet_orchestrator_list = CANISTER_DATA - .with_borrow(|canister_data| canister_data.all_subnet_orchestrator_canisters_list.clone()); + .with_borrow(|canister_data| canister_data.subnet_orchestrators().clone()); for subnet_orchestrator in subnet_orchestrator_list { let result: CallResult<()> = call( diff --git a/src/canister/platform_orchestrator/src/api/canister_management/update_profile_owner_for_individual_users.rs b/src/canister/platform_orchestrator/src/api/canister_management/update_profile_owner_for_individual_users.rs index b931d98c..7c7d9856 100644 --- a/src/canister/platform_orchestrator/src/api/canister_management/update_profile_owner_for_individual_users.rs +++ b/src/canister/platform_orchestrator/src/api/canister_management/update_profile_owner_for_individual_users.rs @@ -5,7 +5,7 @@ use crate::CANISTER_DATA; #[update] fn update_profile_owner_for_individual_canisters() { CANISTER_DATA.with_borrow(|canister_data| { - canister_data.all_subnet_orchestrator_canisters_list.iter().for_each(|subnet_canster_id| { + canister_data.subnet_orchestrators().iter().for_each(|subnet_canster_id| { let _ = ic_cdk::notify(*subnet_canster_id, "update_profile_owner_for_individual_canisters", ()); }) }) diff --git a/src/canister/platform_orchestrator/src/api/canister_management/update_timers_for_hon_game.rs b/src/canister/platform_orchestrator/src/api/canister_management/update_timers_for_hon_game.rs index 3a5127b9..c8b8a1fe 100644 --- a/src/canister/platform_orchestrator/src/api/canister_management/update_timers_for_hon_game.rs +++ b/src/canister/platform_orchestrator/src/api/canister_management/update_timers_for_hon_game.rs @@ -6,7 +6,7 @@ use crate::{guard::is_caller::is_caller_global_admin_or_controller, CANISTER_DAT #[update(guard = "is_caller_global_admin_or_controller")] async fn update_restart_timers_hon_game() -> Result { let subnet_orchestrator_list = CANISTER_DATA - .with_borrow(|canister_data| canister_data.all_subnet_orchestrator_canisters_list.clone()); + .with_borrow(|canister_data| canister_data.subnet_orchestrators().clone()); for subnet_orchestrator in subnet_orchestrator_list { let result: CallResult<()> = call( diff --git a/src/canister/platform_orchestrator/src/api/canister_management/upgrade_canisters_in_network.rs b/src/canister/platform_orchestrator/src/api/canister_management/upgrade_canisters_in_network.rs index 72b92437..3f4664de 100644 --- a/src/canister/platform_orchestrator/src/api/canister_management/upgrade_canisters_in_network.rs +++ b/src/canister/platform_orchestrator/src/api/canister_management/upgrade_canisters_in_network.rs @@ -62,7 +62,7 @@ async fn upgrade_individual_canisters(upgrade_arg: UpgradeCanisterArg) { canister_data.last_subnet_canister_upgrade_status.count = 0; }); let subnet_orchestrator_canisters = CANISTER_DATA - .with_borrow(|canister_data| canister_data.all_subnet_orchestrator_canisters_list.clone()); + .with_borrow(|canister_data| canister_data.subnet_orchestrators().clone()); for subnet_orchestrator in subnet_orchestrator_canisters.iter() { match recharge_subnet_orchestrator_if_needed(*subnet_orchestrator).await { @@ -118,9 +118,9 @@ async fn upgrade_subnet_canisters(upgrade_arg: UpgradeCanisterArg) { let canister_list = CANISTER_DATA.with_borrow(|canister_data| { match upgrade_arg.canister { - WasmType::PostCacheWasm => Ok(canister_data.all_post_cache_orchestrator_list.clone()), + WasmType::PostCacheWasm => Ok(canister_data.post_cache_orchestrators().clone()), WasmType::SubnetOrchestratorWasm => { - Ok(canister_data.all_subnet_orchestrator_canisters_list.clone()) + Ok(canister_data.subnet_orchestrators().clone()) } _ => Err(()), } @@ -256,7 +256,7 @@ async fn upgrade_subnet_orchestrator_canister( ) -> Result<(), String> { // TODO: remove this when all subnet orchestrators are upgraded let mut proof_of_participation = ProofOfParticipation::new_for_root(); - proof_of_participation = proof_of_participation.derive_for_child(canister_id).await?; + proof_of_participation = proof_of_participation.derive_for_child(&CANISTER_DATA, canister_id).await?; let install_code_arg = InstallCodeArgument { mode: CanisterInstallMode::Upgrade(None), diff --git a/src/canister/platform_orchestrator/src/api/canister_management/upgrade_specific_individual_canister.rs b/src/canister/platform_orchestrator/src/api/canister_management/upgrade_specific_individual_canister.rs index 668f0857..89588546 100644 --- a/src/canister/platform_orchestrator/src/api/canister_management/upgrade_specific_individual_canister.rs +++ b/src/canister/platform_orchestrator/src/api/canister_management/upgrade_specific_individual_canister.rs @@ -6,7 +6,7 @@ use crate::{guard::is_caller::is_caller_global_admin_or_controller, CANISTER_DAT #[update(guard = "is_caller_global_admin_or_controller")] fn upgrade_specific_individual_canister(individual_canister_id: Principal) { CANISTER_DATA.with_borrow(|canister_data| { - canister_data.all_subnet_orchestrator_canisters_list.iter().for_each(|subnet_id| { + canister_data.subnet_orchestrators().iter().for_each(|subnet_id| { let _ = ic_cdk::notify(*subnet_id, "upgrade_specific_individual_user_canister_with_latest_wasm", (individual_canister_id, )); }) }) diff --git a/src/canister/platform_orchestrator/src/api/cycle_management/start_reclaiming_cycles_from_individual_canisters.rs b/src/canister/platform_orchestrator/src/api/cycle_management/start_reclaiming_cycles_from_individual_canisters.rs index 1d9eb259..5bd3c0fb 100644 --- a/src/canister/platform_orchestrator/src/api/cycle_management/start_reclaiming_cycles_from_individual_canisters.rs +++ b/src/canister/platform_orchestrator/src/api/cycle_management/start_reclaiming_cycles_from_individual_canisters.rs @@ -6,7 +6,7 @@ use crate::{guard::is_caller::is_caller_global_admin_or_controller, CANISTER_DAT fn start_reclaiming_cycles_from_individual_canisters() -> Result { CANISTER_DATA.with_borrow(|canister_data| { canister_data - .all_subnet_orchestrator_canisters_list + .subnet_orchestrators() .iter() .for_each(|subnet_orchestrator_id| { ic_cdk::notify( diff --git a/src/canister/platform_orchestrator/src/api/cycle_management/start_reclaiming_cycles_from_subnet_orchestrator_canister.rs b/src/canister/platform_orchestrator/src/api/cycle_management/start_reclaiming_cycles_from_subnet_orchestrator_canister.rs index 94a76be7..e2645f3d 100644 --- a/src/canister/platform_orchestrator/src/api/cycle_management/start_reclaiming_cycles_from_subnet_orchestrator_canister.rs +++ b/src/canister/platform_orchestrator/src/api/cycle_management/start_reclaiming_cycles_from_subnet_orchestrator_canister.rs @@ -7,7 +7,7 @@ use crate::{guard::is_caller::is_caller_global_admin_or_controller, CANISTER_DAT #[update(guard = "is_caller_global_admin_or_controller")] async fn start_reclaiming_cycles_from_subnet_orchestrator_canister() -> String { CANISTER_DATA.with_borrow(|canister_data| { - canister_data.all_subnet_orchestrator_canisters_list.iter().for_each(|subnet_orchestrator_id| { + canister_data.subnet_orchestrators().iter().for_each(|subnet_orchestrator_id| { ic_cdk::notify(*subnet_orchestrator_id, "return_cycles_to_platform_orchestrator_canister", ()).unwrap(); }); }); diff --git a/src/canister/platform_orchestrator/src/data_model/mod.rs b/src/canister/platform_orchestrator/src/data_model/mod.rs index a5387e33..01739709 100644 --- a/src/canister/platform_orchestrator/src/data_model/mod.rs +++ b/src/canister/platform_orchestrator/src/data_model/mod.rs @@ -16,8 +16,12 @@ use shared_utils::{ SubnetUpgradeReport, }, }, - common::types::wasm::{CanisterWasm, WasmType}, - types::creator_dao_stats::CreatorDaoTokenStats, + common::{ + participant_crypto::{merkle::ChildrenMerkle, ProofOfParticipationDeriverStore}, + types::{ + wasm::{CanisterWasm, WasmType}, + creator_dao_stats::CreatorDaoTokenStats, + }, }; use self::memory::{ @@ -34,8 +38,8 @@ pub struct StateGuard { #[derive(Serialize, Deserialize)] pub struct CanisterData { - pub all_subnet_orchestrator_canisters_list: HashSet, - pub all_post_cache_orchestrator_list: HashSet, + all_subnet_orchestrator_canisters_list: HashSet, + all_post_cache_orchestrator_list: HashSet, pub subet_orchestrator_with_capacity_left: HashSet, pub version_detail: VersionDetails, #[serde(skip, default = "_default_wasms")] @@ -53,6 +57,8 @@ pub struct CanisterData { pub state_guard: StateGuard, #[serde(default)] pub creator_dao_stats: CreatorDaoTokenStats, + #[serde(default)] + pub children_merkle: ChildrenMerkle, } fn _default_wasms() -> StableBTreeMap { @@ -82,10 +88,55 @@ impl Default for CanisterData { subnets_upgrade_report: SubnetUpgradeReport::default(), state_guard: StateGuard::default(), creator_dao_stats: CreatorDaoTokenStats::default(), + children_merkle: ChildrenMerkle::default(), } } } +impl CanisterData { + pub fn insert_subnet_orchestrator_and_post_cache(&mut self, subnet_orchestrator: Principal, post_cache: Principal) { + self.all_subnet_orchestrator_canisters_list.insert(subnet_orchestrator); + self.all_post_cache_orchestrator_list.insert(post_cache); + self.subet_orchestrator_with_capacity_left.insert(subnet_orchestrator); + self.children_merkle.insert_children([subnet_orchestrator, post_cache]); + } + + pub fn insert_subnet_orchestrator(&mut self, subnet_orchestrator: Principal, provisioning_available: bool) { + self.all_subnet_orchestrator_canisters_list.insert(subnet_orchestrator); + if provisioning_available { + self.subet_orchestrator_with_capacity_left.insert(subnet_orchestrator); + } + self.children_merkle.insert_children([subnet_orchestrator]); + } + + pub fn remove_subnet_orchestrator(&mut self, subnet_orchestrator: Principal, remove_it_completely: bool) { + self.subet_orchestrator_with_capacity_left.remove(&subnet_orchestrator); + if remove_it_completely { + self.all_subnet_orchestrator_canisters_list.remove(&subnet_orchestrator); + // WARN: does not revoke their Proof of Participation + self.children_merkle.remove_child(subnet_orchestrator); + } + } + + pub fn subnet_orchestrators(&self) -> &HashSet { + &self.all_subnet_orchestrator_canisters_list + } + + pub fn post_cache_orchestrators(&self) -> &HashSet { + &self.all_post_cache_orchestrator_list + } +} + +impl ProofOfParticipationDeriverStore for CanisterData { + fn children_merkle(&self) -> &ChildrenMerkle { + &self.children_merkle + } + + fn children_merkle_mut(&mut self) -> &mut ChildrenMerkle { + &mut self.children_merkle + } +} + #[derive(Serialize, Deserialize, CandidType, Clone)] pub struct VersionDetails { pub version: String, diff --git a/src/canister/platform_orchestrator/src/utils/registered_subnet_orchestrator.rs b/src/canister/platform_orchestrator/src/utils/registered_subnet_orchestrator.rs index 6de37f92..3dde04c8 100644 --- a/src/canister/platform_orchestrator/src/utils/registered_subnet_orchestrator.rs +++ b/src/canister/platform_orchestrator/src/utils/registered_subnet_orchestrator.rs @@ -63,7 +63,7 @@ impl RegisteredSubnetOrchestrator { pub fn new(canister_id: Principal) -> Result { let contains = CANISTER_DATA.with_borrow(|canister_data| { canister_data - .all_subnet_orchestrator_canisters_list + .subnet_orchestrators() .contains(&canister_id) }); diff --git a/src/canister/user_index/can.did b/src/canister/user_index/can.did index 6e5b0a75..7770e728 100644 --- a/src/canister/user_index/can.did +++ b/src/canister/user_index/can.did @@ -63,7 +63,12 @@ type KnownPrincipalType = variant { UserIdGlobalSuperAdmin; }; type LogVisibility = variant { controllers; public }; -type ProofOfChild = record { "principal" : principal; signature : blob }; +type ProofOfChild = record { + proof_of_inclusion : vec blob; + "principal" : principal; + children_proof : ProofOfChildren; +}; +type ProofOfChildren = record { signature : blob; merkle_root : blob }; type ProofOfParticipation = record { chain : vec ProofOfChild }; type QueryStats = record { response_payload_bytes_total : nat; diff --git a/src/canister/user_index/src/api/canister_management/create_pool_of_available_canisters.rs b/src/canister/user_index/src/api/canister_management/create_pool_of_available_canisters.rs index b3fdb53b..dfd2bd60 100644 --- a/src/canister/user_index/src/api/canister_management/create_pool_of_available_canisters.rs +++ b/src/canister/user_index/src/api/canister_management/create_pool_of_available_canisters.rs @@ -1,16 +1,10 @@ -use std::pin::Pin; - -use futures::Future; +use futures::StreamExt; use ic_cdk::{api::is_controller, caller}; -use shared_utils::{common::{types::wasm::{CanisterWasm, WasmType}, utils::task::run_task_concurrently}, constant::{get_backup_individual_user_canister_batch_size, get_backup_individual_user_canister_threshold, get_individual_user_canister_subnet_batch_size}}; +use shared_utils::{common::types::wasm::{CanisterWasm, WasmType}, constant::{get_backup_individual_user_canister_batch_size, get_backup_individual_user_canister_threshold, get_individual_user_canister_subnet_batch_size}}; use ic_cdk_macros::update; -use crate::{util::canister_management::{create_empty_user_canister, create_users_canister}, CANISTER_DATA}; +use crate::{util::canister_management::{create_empty_user_canister, install_canister_wasm, recharge_canister_for_installing_wasm}, CANISTER_DATA}; -enum CanisterCodeState { - Empty, - WasmInstalled -} #[update] pub fn create_pool_of_individual_user_available_canisters(version: String, individual_user_wasm: Vec) -> Result { @@ -35,34 +29,53 @@ pub fn create_pool_of_individual_user_available_canisters(version: String, indiv pub async fn impl_create_pool_of_individual_user_available_canisters(version: String, individual_user_wasm: Vec) { let backup_individual_user_canister_batch_size = get_backup_individual_user_canister_batch_size(); + let individual_user_canister_subnet_batch_size = get_individual_user_canister_subnet_batch_size(); + let total_cnt = backup_individual_user_canister_batch_size + individual_user_canister_subnet_batch_size; //empty canister for backup - let create_empty_canister_futures = (0..backup_individual_user_canister_batch_size) - .map(|_| Box::pin(async { - let canister_id = create_empty_user_canister().await; - (canister_id, CanisterCodeState::Empty) - }) as Pin>>); - - //canisters with installed wasm for available pool - let individual_user_canister_subnet_batch_size = get_individual_user_canister_subnet_batch_size(); - let create_canister_with_wasm_futures = (0..individual_user_canister_subnet_batch_size) - .map(|_| Box::pin(async { - let canister_id = create_users_canister(None, version.clone(), individual_user_wasm.clone()).await; - (canister_id, CanisterCodeState::WasmInstalled) - }) as Pin>>); + let create_empty_canister_futures = (0..total_cnt) + .map(|_| create_empty_user_canister()); + let mut cans_stream = futures::stream::iter(create_empty_canister_futures).buffer_unordered(10); - let combined_create_canister_futures = create_canister_with_wasm_futures.chain(create_empty_canister_futures); + let to_install: Vec<_> = cans_stream.by_ref().take(individual_user_canister_subnet_batch_size as usize).collect().await; + CANISTER_DATA.with_borrow_mut(|cdata| { + cdata.children_merkle.insert_children(to_install.clone()); + }); - run_task_concurrently(combined_create_canister_futures, - 10, - |(canister_id, canister_code_state)| { - CANISTER_DATA.with_borrow_mut(|canister_data| { - match canister_code_state { - CanisterCodeState::Empty => canister_data.backup_canister_pool.insert(canister_id), - CanisterCodeState::WasmInstalled => canister_data.available_canisters.insert(canister_id) - }; - }); - }, - || false).await; + //canisters with installed wasm for available pool + let install_wasm_futs = to_install.into_iter().map(move |canister_id| { + let version = version.clone(); + let individual_user_wasm = individual_user_wasm.clone(); + async move { + recharge_canister_for_installing_wasm(canister_id).await.map_err(|e| (canister_id, e))?; + install_canister_wasm( + canister_id, + None, + version, + individual_user_wasm, + ).await + } + }); + let mut install_wasm_stream = futures::stream::iter(install_wasm_futs).buffer_unordered(10); + while let Some(res) = install_wasm_stream.next().await { + match res { + Ok(canister_id) => { + CANISTER_DATA.with_borrow_mut(|cdata| { + cdata.available_canisters.insert(canister_id); + }) + } + Err((canister_id, e)) => { + ic_cdk::println!("Failed to install wasm on canister: {:?}, error: {:?}", canister_id, e); + CANISTER_DATA.with_borrow_mut(|cdata| { + cdata.insert_backup_canister(canister_id); + }) + } + } + } + while let Some(canister_id) = cans_stream.next().await { + CANISTER_DATA.with_borrow_mut(|cdata| { + cdata.insert_backup_canister(canister_id); + }) + } } \ No newline at end of file diff --git a/src/canister/user_index/src/api/canister_management/get_subnet_backup_capacity.rs b/src/canister/user_index/src/api/canister_management/get_subnet_backup_capacity.rs index 5d33afa6..dba6de49 100644 --- a/src/canister/user_index/src/api/canister_management/get_subnet_backup_capacity.rs +++ b/src/canister/user_index/src/api/canister_management/get_subnet_backup_capacity.rs @@ -1,4 +1,3 @@ -use std::borrow::Borrow; use ic_cdk_macros::query; use crate::CANISTER_DATA; @@ -7,6 +6,6 @@ use crate::CANISTER_DATA; #[query] pub fn get_subnet_backup_capacity() -> u64 { CANISTER_DATA.with_borrow(|canister_data| { - canister_data.borrow().backup_canister_pool.len() as u64 + canister_data.backup_canisters().len() as u64 }) } \ No newline at end of file diff --git a/src/canister/user_index/src/api/upgrade_individual_user_template/update_user_index_upgrade_user_canisters_with_latest_wasm.rs b/src/canister/user_index/src/api/upgrade_individual_user_template/update_user_index_upgrade_user_canisters_with_latest_wasm.rs index 20b87564..256a65dd 100644 --- a/src/canister/user_index/src/api/upgrade_individual_user_template/update_user_index_upgrade_user_canisters_with_latest_wasm.rs +++ b/src/canister/user_index/src/api/upgrade_individual_user_template/update_user_index_upgrade_user_canisters_with_latest_wasm.rs @@ -51,6 +51,13 @@ pub async fn upgrade_user_canisters_with_latest_wasm( }) }); + // TODO: remove this after upgrade is executed on all individual canisters + CANISTER_DATA.with_borrow_mut(|cdata| { + cdata.children_merkle.insert_children( + user_principal_id_to_canister_id_vec.iter().map(|(_, canister_id)| *canister_id) + ); + }); + let saved_upgrade_status = CANISTER_DATA.with(|canister_data_ref_cell| { canister_data_ref_cell .borrow() @@ -213,7 +220,7 @@ async fn upgrade_user_canister( // TODO: remove this after upgrade is executed on all individual canisters let mut proof_of_participation = CANISTER_DATA.with_borrow(|cdata| cdata.proof_of_participation.clone()); if let Some(pop) = proof_of_participation { - proof_of_participation = Some(pop.derive_for_child(canister_id).await?); + proof_of_participation = Some(pop.derive_for_child(&CANISTER_DATA, canister_id).await?); } canister_management::upgrade_individual_user_canister( diff --git a/src/canister/user_index/src/api/upgrade_individual_user_template/upgrade_specific_individual_user_canister_with_latest_wasm.rs b/src/canister/user_index/src/api/upgrade_individual_user_template/upgrade_specific_individual_user_canister_with_latest_wasm.rs index af9d5965..4b77548f 100644 --- a/src/canister/user_index/src/api/upgrade_individual_user_template/upgrade_specific_individual_user_canister_with_latest_wasm.rs +++ b/src/canister/user_index/src/api/upgrade_individual_user_template/upgrade_specific_individual_user_canister_with_latest_wasm.rs @@ -51,9 +51,14 @@ async fn upgrade_specific_individual_user_canister_with_latest_wasm( recharge_canister_for_installing_wasm(user_canister_id).await?; - let mut proof_of_participation = CANISTER_DATA.with_borrow(|cdata| cdata.proof_of_participation.clone()); + // TODO: remove this after upgrade is executed on all individual canisters + let mut proof_of_participation = CANISTER_DATA.with_borrow_mut(|cdata| { + cdata.children_merkle.insert_children([user_canister_id]); + + cdata.proof_of_participation.clone() + }); if let Some(pop) = proof_of_participation { - proof_of_participation = Some(pop.derive_for_child(user_canister_id).await.unwrap()); + proof_of_participation = Some(pop.derive_for_child(&CANISTER_DATA, user_canister_id).await.unwrap()); } match canister_management::upgrade_individual_user_canister( diff --git a/src/canister/user_index/src/api/user_record/get_requester_principals_canister_id_create_if_not_exists_and_optionally_allow_referrer.rs b/src/canister/user_index/src/api/user_record/get_requester_principals_canister_id_create_if_not_exists_and_optionally_allow_referrer.rs index 81710b81..8865358a 100644 --- a/src/canister/user_index/src/api/user_record/get_requester_principals_canister_id_create_if_not_exists_and_optionally_allow_referrer.rs +++ b/src/canister/user_index/src/api/user_record/get_requester_principals_canister_id_create_if_not_exists_and_optionally_allow_referrer.rs @@ -152,7 +152,7 @@ async fn new_user_signup(user_id: Principal) -> Result { let available_individual_user_canisters_cnt = CANISTER_DATA.with_borrow(|canister_data| canister_data.available_canisters.len() as u64); let backup_individual_user_canister_cnt = - CANISTER_DATA.with_borrow(|canister_data| canister_data.backup_canister_pool.len() as u64); + CANISTER_DATA.with_borrow(|canister_data| canister_data.backup_canisters().len() as u64); // notify platform_orchestrator that this subnet has reached maximum capacity. if available_individual_user_canisters_cnt == 0 && backup_individual_user_canister_cnt == 0 { @@ -190,7 +190,7 @@ async fn new_user_signup(user_id: Principal) -> Result { async fn provision_new_available_canisters(individual_user_template_canister_wasm: CanisterWasm) { let backup_pool_canister_count = - CANISTER_DATA.with_borrow(|canister_data| canister_data.backup_canister_pool.len() as u64); + CANISTER_DATA.with_borrow(|canister_data| canister_data.backup_canisters().len() as u64); let individual_canister_batch_size = get_individual_user_canister_subnet_batch_size(); let canister_count = individual_canister_batch_size.min(backup_pool_canister_count); let available_canister_count = @@ -198,7 +198,7 @@ async fn provision_new_available_canisters(individual_user_template_canister_was let max_canister_count = available_canister_count + canister_count; let install_canister_wasm_futures = CANISTER_DATA.with_borrow(|canister_data| { - let mut backup_pool_canister = canister_data.backup_canister_pool.clone().into_iter(); + let mut backup_pool_canister = canister_data.backup_canisters().clone().into_iter(); (0..canister_count).map(move |_| { let individual_user_template_canister_wasm_version = individual_user_template_canister_wasm.version.clone(); @@ -210,7 +210,7 @@ async fn provision_new_available_canisters(individual_user_template_canister_was // Remove the canister id from backup pool so no one else access it CANISTER_DATA.with_borrow_mut(|canister_data| { - canister_data.backup_canister_pool.remove(&canister_id) + canister_data.remove_backup_canister(&canister_id) }); async move { @@ -236,7 +236,7 @@ async fn provision_new_available_canisters(individual_user_template_canister_was canister_data.available_canisters.insert(canister_id); } Err(e) => { - canister_data.backup_canister_pool.insert(e.0); + canister_data.reinsert_backup_canister_due_to_failure(e.0); ic_cdk::println!("Error installing wasm for canister {} {}", e.0, e.1); } }) @@ -260,7 +260,7 @@ async fn provision_new_available_canisters(individual_user_template_canister_was async fn provision_new_backup_canisters(canister_count: u64) { let breaking_condition = || { CANISTER_DATA.with_borrow(|canister_data| { - canister_data.backup_canister_pool.len() as u64 + canister_data.backup_canisters().len() as u64 > get_backup_individual_user_canister_batch_size() }) }; diff --git a/src/canister/user_index/src/api/user_signup/are_signups_enabled.rs b/src/canister/user_index/src/api/user_signup/are_signups_enabled.rs index 4e6aad73..eb391238 100644 --- a/src/canister/user_index/src/api/user_signup/are_signups_enabled.rs +++ b/src/canister/user_index/src/api/user_signup/are_signups_enabled.rs @@ -25,15 +25,13 @@ mod test { #[test] fn test_are_signups_enabled_impl() { - let mut canister_data = CanisterData { - configuration: Configuration { + let mut canister_data = CanisterData::default(); + canister_data.configuration = Configuration { known_principal_ids: HashMap::default(), signups_open_on_this_subnet: true, url_to_send_canister_metrics_to: String::from("http://example.com") - }, - ..Default::default() }; - + assert!(are_signups_enabled_impl(&canister_data)); canister_data.configuration.signups_open_on_this_subnet = false; diff --git a/src/canister/user_index/src/data_model/mod.rs b/src/canister/user_index/src/data_model/mod.rs index 206ea0a9..3b0ceaa3 100644 --- a/src/canister/user_index/src/data_model/mod.rs +++ b/src/canister/user_index/src/data_model/mod.rs @@ -6,7 +6,8 @@ use serde::Serialize; use shared_utils::canister_specific::user_index::types::{ BroadcastCallStatus, RecycleStatus, UpgradeStatus, }; -use shared_utils::common::participant_crypto::ProofOfParticipation; +use shared_utils::common::participant_crypto::merkle::ChildrenMerkle; +use shared_utils::common::participant_crypto::{ProofOfParticipation, ProofOfParticipationDeriverStore}; use shared_utils::common::types::wasm::{CanisterWasm, WasmType}; use self::memory::get_wasm_memory; @@ -30,7 +31,7 @@ pub struct CanisterData { pub allow_upgrades_for_individual_canisters: bool, pub available_canisters: HashSet, #[serde(default)] - pub backup_canister_pool: HashSet, + backup_canister_pool: HashSet, pub user_principal_id_to_canister_id_map: BTreeMap, pub unique_user_name_to_user_principal_id_map: BTreeMap, #[serde(skip, default = "_empty_wasms")] @@ -41,6 +42,8 @@ pub struct CanisterData { pub last_broadcast_call_status: BroadcastCallStatus, #[serde(default)] pub proof_of_participation: Option, + #[serde(default)] + pub children_merkle: ChildrenMerkle, } impl Default for CanisterData { @@ -57,7 +60,46 @@ impl Default for CanisterData { recycle_status: Default::default(), last_broadcast_call_status: Default::default(), proof_of_participation: None, + children_merkle: ChildrenMerkle::default(), + } + } +} + +impl CanisterData { + pub fn insert_backup_canister(&mut self, canister_id: Principal) -> bool { + let inserted = self.backup_canister_pool.insert(canister_id); + if inserted { + self.children_merkle.insert_children([canister_id]); } + inserted + } + + pub fn remove_backup_canister(&mut self, canister_id: &Principal) { + self.backup_canister_pool.remove(&canister_id); + // removal from backup pool does not mean its not part of our fleet + // an individual canister might be installed on it instead, for example + // so we don't remove it from children_merkle + } + + // reinsert a previously removed backup canister + // caller MUST be careful and only call this function if the canister was previously removed + // USE [`CanisterData::insert_backup_canister`] if you are not sure + pub fn reinsert_backup_canister_due_to_failure(&mut self, canister_id: Principal) { + self.backup_canister_pool.insert(canister_id); + } + + pub fn backup_canisters(&self) -> &HashSet { + &self.backup_canister_pool + } +} + +impl ProofOfParticipationDeriverStore for CanisterData { + fn children_merkle(&self) -> &ChildrenMerkle { + &self.children_merkle + } + + fn children_merkle_mut(&mut self) -> &mut ChildrenMerkle { + &mut self.children_merkle } } diff --git a/src/canister/user_index/src/util/canister_management.rs b/src/canister/user_index/src/util/canister_management.rs index 4f363a58..224ae617 100644 --- a/src/canister/user_index/src/util/canister_management.rs +++ b/src/canister/user_index/src/util/canister_management.rs @@ -49,17 +49,6 @@ struct CustomInstallCodeArgument { pub unsafe_drop_stable_memory: Option, } -pub async fn create_users_canister( - profile_owner: Option, - version: String, - individual_user_wasm: Vec, -) -> Principal { - let canister_id = create_empty_user_canister().await; - recharge_canister_for_installing_wasm(canister_id).await; - install_canister_wasm(canister_id, profile_owner, version, individual_user_wasm).await; - canister_id -} - pub async fn create_empty_user_canister() -> Principal { // * config for provisioning canister let arg = CreateCanisterArgument { @@ -95,7 +84,7 @@ pub async fn provision_number_of_empty_canisters( let result_callback = |canister_id: Principal| { CANISTER_DATA.with_borrow_mut(|canister_data| { - canister_data.backup_canister_pool.insert(canister_id) + canister_data.insert_backup_canister(canister_id) }); }; @@ -119,7 +108,7 @@ pub async fn install_canister_wasm( let mut proof_of_participation = CANISTER_DATA.with_borrow(|cdata| cdata.proof_of_participation.clone()); if let Some(pop) = proof_of_participation { - proof_of_participation = Some(pop.derive_for_child(canister_id).await.unwrap()); + proof_of_participation = Some(pop.derive_for_child(&CANISTER_DATA, canister_id).await.unwrap()); } let individual_user_tempalate_init_args = IndividualUserTemplateInitArgs { profile_owner, @@ -164,7 +153,7 @@ pub async fn reinstall_canister_wasm( let mut proof_of_participation = CANISTER_DATA.with_borrow(|cdata| cdata.proof_of_participation.clone()); if let Some(pop) = proof_of_participation { - proof_of_participation = Some(pop.derive_for_child(canister_id).await?); + proof_of_participation = Some(pop.derive_for_child(&CANISTER_DATA, canister_id).await?); } let individual_user_tempalate_init_args = IndividualUserTemplateInitArgs { profile_owner, diff --git a/src/canister/user_index/src/util/types/individual_user_canister.rs b/src/canister/user_index/src/util/types/individual_user_canister.rs index 0bb389b2..0cbaa067 100644 --- a/src/canister/user_index/src/util/types/individual_user_canister.rs +++ b/src/canister/user_index/src/util/types/individual_user_canister.rs @@ -97,12 +97,12 @@ impl IndividualUserCanister { pub async fn allot_empty_canister(&self) -> Result { let alloted_canister_id_res = CANISTER_DATA.with_borrow_mut(|canister_data| { - let Some(new_canister_id) = canister_data.backup_canister_pool.iter().next().copied() + let Some(new_canister_id) = canister_data.backup_canisters().iter().next().copied() else { return Err("No Backup Canisters Available".into()); }; - canister_data.backup_canister_pool.remove(&new_canister_id); + canister_data.remove_backup_canister(&new_canister_id); Ok(new_canister_id) }); @@ -119,7 +119,7 @@ impl IndividualUserCanister { .await .inspect_err(|_| { CANISTER_DATA.with_borrow_mut(|canister_data| { - canister_data.backup_canister_pool.insert(canister_id) + canister_data.reinsert_backup_canister_due_to_failure(canister_id) }); }) .map_err(|e| e.1)?; diff --git a/src/lib/shared_utils/Cargo.toml b/src/lib/shared_utils/Cargo.toml index 065ec574..89bb3ea9 100644 --- a/src/lib/shared_utils/Cargo.toml +++ b/src/lib/shared_utils/Cargo.toml @@ -17,7 +17,16 @@ ciborium = { workspace = true } serde_json_any_key = "2.0.0" serde_bytes = "0.11.14" icrc-ledger-types = { workspace = true } -ed25519-dalek = "2.1.1" +ed25519-compact = { version = "2.1.1", default-features = false, features = ["std"] } +trie-db = { version = "0.29.1", default-features = false } +hash-db = { version = "0.16.0", default-features = false } +blake3 = "1.5.4" +hash256-std-hasher = { version = "0.15.2", default-features = false } +parity-scale-codec = { version = "3.6.12", default-features = false } +memory-db = { version = "0.32.0", default-features = false } +trie-root = { version = "0.18.0", default-features = false } +# monotree = { git = "https://github.com/rupansh-sekar-yral/monotree.git", rev = "1504007dae91a909fbb29bfebb36181ef3333cb8" } +# hashbrown = { version = "0.7", features = ["serde"] } [dev-dependencies] test_utils = { workspace = true } diff --git a/src/lib/shared_utils/src/common/participant_crypto/merkle/backing.rs b/src/lib/shared_utils/src/common/participant_crypto/merkle/backing.rs new file mode 100644 index 00000000..1b087bf4 --- /dev/null +++ b/src/lib/shared_utils/src/common/participant_crypto/merkle/backing.rs @@ -0,0 +1,113 @@ +use std::collections::{btree_map::Entry, BTreeMap}; + +use hash_db::{AsHashDB, HashDB, HashDBRef, Hasher, Prefix}; +use memory_db::{HashKey, KeyFunction}; +use serde::{Deserialize, Serialize}; +use trie_db::{TrieLayout, NodeCodec}; + +use super::{Blake3Hasher, ChildreenTreeLayout, Hash}; + +type ChildrenHK = HashKey; + +// https://docs.rs/memory-db/0.32.0/src/memory_db/lib.rs.html#83-92 modified for our uses +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ChildrenBacking { + data: BTreeMap, i32)>, + hashed_null_node: Hash, + null_node_data: Vec, +} + +impl Default for ChildrenBacking { + fn default() -> Self { + Self { + data: BTreeMap::new(), + hashed_null_node: ::Codec::hashed_null_node(), + null_node_data: ::Codec::empty_node().to_vec(), + } + } +} + +impl HashDB> for ChildrenBacking { + fn get(&self, key: &Hash, prefix: Prefix) -> Option> { + if key == &self.hashed_null_node { + return Some(self.null_node_data.clone()); + } + + let key = ChildrenHK::key(key, prefix); + match self.data.get(&key) { + Some(&(ref v, rc)) if rc > 0 => Some(v.clone()), + _ => None, + } + } + + fn contains(&self, key: &Hash, prefix: Prefix) -> bool { + if key == &self.hashed_null_node { + return true; + } + + let key = ChildrenHK::key(key, prefix); + self.data.get(&key).map(|&(_, rc)| rc > 0).unwrap_or_default() + } + + fn emplace(&mut self, key: Hash, prefix: Prefix, value: Vec) { + if value == self.null_node_data { + return; + } + + let key = ChildrenHK::key(&key, prefix); + match self.data.entry(key) { + Entry::Occupied(mut entry) => { + let &mut (ref mut old_value, ref mut rc) = entry.get_mut(); + if *rc <= 0 { + *old_value = value; + } + *rc += 1; + }, + Entry::Vacant(entry) => { + entry.insert((value, 1)); + } + } + } + + fn insert(&mut self, prefix: Prefix, value: &[u8]) -> Hash { + if value == self.null_node_data { + return self.hashed_null_node; + } + + let key = Blake3Hasher::hash(value); + self.emplace(key, prefix, value.to_vec()); + key + } + + fn remove(&mut self, key: &Hash, prefix: Prefix) { + if key == &self.hashed_null_node { + return; + } + + let key = ChildrenHK::key(key, prefix); + self.data.entry(key) + .and_modify(|&mut (_, ref mut rc)| *rc -= 1) + .or_insert_with(|| (vec![], -1)); + } +} + +impl AsHashDB> for ChildrenBacking { + fn as_hash_db(&self) -> &dyn HashDB> { + self + } + + fn as_hash_db_mut<'a>(&'a mut self) -> &'a mut (dyn HashDB> + 'a) { + self + } +} + +impl HashDBRef> for ChildrenBacking { + fn get(&self, key: &Hash, prefix: Prefix) -> Option> { + HashDB::get(self, key, prefix) + } + + fn contains(&self, key: &Hash, prefix: Prefix) -> bool { + HashDB::contains(self, key, prefix) + } +} + diff --git a/src/lib/shared_utils/src/common/participant_crypto/merkle/layout.rs b/src/lib/shared_utils/src/common/participant_crypto/merkle/layout.rs new file mode 100644 index 00000000..41734d0e --- /dev/null +++ b/src/lib/shared_utils/src/common/participant_crypto/merkle/layout.rs @@ -0,0 +1,708 @@ +// Taken directly from https://github.com/paritytech/trie/blob/master/test-support/reference-trie/src/substrate.rs + +use core::{borrow::Borrow, iter::once, marker::PhantomData, ops::Range}; +use hash_db::Hasher; +use parity_scale_codec as codec; +use parity_scale_codec::{Compact, Decode, Encode, Input, Output}; +use trie_db::{ + nibble_ops, + node::{NibbleSlicePlan, NodeHandlePlan, NodePlan, Value, ValuePlan}, + ChildReference, NodeCodec as NodeCodecT, TrieConfiguration, TrieLayout, +}; + +/// Constants used into trie simplification codec. +mod trie_constants { + const FIRST_PREFIX: u8 = 0b_00 << 6; + pub const LEAF_PREFIX_MASK: u8 = 0b_01 << 6; + pub const BRANCH_WITHOUT_MASK: u8 = 0b_10 << 6; + pub const BRANCH_WITH_MASK: u8 = 0b_11 << 6; + pub const EMPTY_TRIE: u8 = FIRST_PREFIX /*| (0b_00 << 4)*/; + pub const ALT_HASHING_LEAF_PREFIX_MASK: u8 = FIRST_PREFIX | (0b_1 << 5); + pub const ALT_HASHING_BRANCH_WITH_MASK: u8 = FIRST_PREFIX | (0b_01 << 4); + pub const ESCAPE_COMPACT_HEADER: u8 = EMPTY_TRIE | 0b_00_01; +} + +pub const TRIE_VALUE_NODE_THRESHOLD: u32 = 33; + +/// Codec-flavored TrieStream. +#[derive(Default, Clone)] +pub struct TrieStream { + /// Current node buffer. + buffer: Vec, +} + +fn branch_node_bit_mask(has_children: impl Iterator) -> (u8, u8) { + let mut bitmap: u16 = 0; + let mut cursor: u16 = 1; + for v in has_children { + if v { + bitmap |= cursor + } + cursor <<= 1; + } + ((bitmap % 256) as u8, (bitmap / 256) as u8) +} + +/// Create a leaf/branch node, encoding a number of nibbles. +fn fuse_nibbles_node(nibbles: &[u8], kind: NodeKind) -> impl Iterator + '_ { + let size = nibbles.len(); + let iter_start = match kind { + NodeKind::Leaf => size_and_prefix_iterator(size, trie_constants::LEAF_PREFIX_MASK, 2), + NodeKind::BranchNoValue => + size_and_prefix_iterator(size, trie_constants::BRANCH_WITHOUT_MASK, 2), + NodeKind::BranchWithValue => + size_and_prefix_iterator(size, trie_constants::BRANCH_WITH_MASK, 2), + NodeKind::HashedValueLeaf => + size_and_prefix_iterator(size, trie_constants::ALT_HASHING_LEAF_PREFIX_MASK, 3), + NodeKind::HashedValueBranch => + size_and_prefix_iterator(size, trie_constants::ALT_HASHING_BRANCH_WITH_MASK, 4), + }; + iter_start + .chain(if nibbles.len() % 2 == 1 { Some(nibbles[0]) } else { None }) + .chain(nibbles[nibbles.len() % 2..].chunks(2).map(|ch| ch[0] << 4 | ch[1])) +} + +use trie_root::Value as TrieStreamValue; +impl trie_root::TrieStream for TrieStream { + fn new() -> Self { + Self { buffer: Vec::new() } + } + + fn append_empty_data(&mut self) { + self.buffer.push(trie_constants::EMPTY_TRIE); + } + + fn append_leaf(&mut self, key: &[u8], value: TrieStreamValue) { + let kind = match &value { + TrieStreamValue::Inline(..) => NodeKind::Leaf, + TrieStreamValue::Node(..) => NodeKind::HashedValueLeaf, + }; + self.buffer.extend(fuse_nibbles_node(key, kind)); + match &value { + TrieStreamValue::Inline(value) => { + Compact(value.len() as u32).encode_to(&mut self.buffer); + self.buffer.extend_from_slice(value); + }, + TrieStreamValue::Node(hash) => { + self.buffer.extend_from_slice(hash.as_slice()); + }, + }; + } + + fn begin_branch( + &mut self, + maybe_partial: Option<&[u8]>, + maybe_value: Option, + has_children: impl Iterator, + ) { + if let Some(partial) = maybe_partial { + let kind = match &maybe_value { + None => NodeKind::BranchNoValue, + Some(TrieStreamValue::Inline(..)) => NodeKind::BranchWithValue, + Some(TrieStreamValue::Node(..)) => NodeKind::HashedValueBranch, + }; + + self.buffer.extend(fuse_nibbles_node(partial, kind)); + let bm = branch_node_bit_mask(has_children); + self.buffer.extend([bm.0, bm.1].iter()); + } else { + unreachable!("trie stream codec only for no extension trie"); + } + match maybe_value { + None => (), + Some(TrieStreamValue::Inline(value)) => { + Compact(value.len() as u32).encode_to(&mut self.buffer); + self.buffer.extend_from_slice(value); + }, + Some(TrieStreamValue::Node(hash)) => { + self.buffer.extend_from_slice(hash.as_slice()); + }, + } + } + + fn append_extension(&mut self, _key: &[u8]) { + debug_assert!(false, "trie stream codec only for no extension trie"); + } + + fn append_substream(&mut self, other: Self) { + let data = other.out(); + match data.len() { + 0..=31 => data.encode_to(&mut self.buffer), + _ => H::hash(&data).as_ref().encode_to(&mut self.buffer), + } + } + + fn out(self) -> Vec { + self.buffer + } +} + +/// Helper struct for trie node decoder. This implements `codec::Input` on a byte slice, while +/// tracking the absolute position. This is similar to `std::io::Cursor` but does not implement +/// `Read` and `io` is not in `sp-std`. +struct ByteSliceInput<'a> { + data: &'a [u8], + offset: usize, +} + +impl<'a> ByteSliceInput<'a> { + fn new(data: &'a [u8]) -> Self { + ByteSliceInput { data, offset: 0 } + } + + fn take(&mut self, count: usize) -> Result, codec::Error> { + if self.offset + count > self.data.len() { + return Err("out of data".into()) + } + + let range = self.offset..(self.offset + count); + self.offset += count; + Ok(range) + } +} + +impl<'a> Input for ByteSliceInput<'a> { + fn remaining_len(&mut self) -> Result, codec::Error> { + Ok(Some(self.data.len().saturating_sub(self.offset))) + } + + fn read(&mut self, into: &mut [u8]) -> Result<(), codec::Error> { + let range = self.take(into.len())?; + into.copy_from_slice(&self.data[range]); + Ok(()) + } + + fn read_byte(&mut self) -> Result { + if self.offset + 1 > self.data.len() { + return Err("out of data".into()) + } + + let byte = self.data[self.offset]; + self.offset += 1; + Ok(byte) + } +} + +/// Concrete implementation of a [`NodeCodecT`] with SCALE encoding. +/// +/// It is generic over `H` the [`Hasher`]. +#[derive(Default, Clone)] +pub struct NodeCodec(PhantomData); + +impl NodeCodecT for NodeCodec +where + H: Hasher, +{ + const ESCAPE_HEADER: Option = Some(trie_constants::ESCAPE_COMPACT_HEADER); + type Error = Error; + type HashOut = H::Out; + + fn hashed_null_node() -> ::Out { + H::hash(::empty_node()) + } + + fn decode_plan(data: &[u8]) -> Result { + let mut input = ByteSliceInput::new(data); + + let header = NodeHeader::decode(&mut input)?; + let contains_hash = header.contains_hash_of_value(); + + let branch_has_value = if let NodeHeader::Branch(has_value, _) = &header { + *has_value + } else { + // hashed_value_branch + true + }; + + match header { + NodeHeader::Null => Ok(NodePlan::Empty), + NodeHeader::HashedValueBranch(nibble_count) | NodeHeader::Branch(_, nibble_count) => { + let padding = nibble_count % nibble_ops::NIBBLE_PER_BYTE != 0; + // check that the padding is valid (if any) + if padding && nibble_ops::pad_left(data[input.offset]) != 0 { + return Err(Error::BadFormat) + } + let partial = input.take( + (nibble_count + (nibble_ops::NIBBLE_PER_BYTE - 1)) / + nibble_ops::NIBBLE_PER_BYTE, + )?; + let partial_padding = nibble_ops::number_padding(nibble_count); + let bitmap_range = input.take(BITMAP_LENGTH)?; + let bitmap = Bitmap::decode(&data[bitmap_range])?; + let value = if branch_has_value { + Some(if contains_hash { + ValuePlan::Node(input.take(H::LENGTH)?) + } else { + let count = >::decode(&mut input)?.0 as usize; + ValuePlan::Inline(input.take(count)?) + }) + } else { + None + }; + let mut children = [ + None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, + ]; + for (i, child) in children.iter_mut().enumerate().take(nibble_ops::NIBBLE_LENGTH) { + if bitmap.value_at(i) { + let count = >::decode(&mut input)?.0 as usize; + let range = input.take(count)?; + *child = Some(if count == H::LENGTH { + NodeHandlePlan::Hash(range) + } else { + NodeHandlePlan::Inline(range) + }); + } + } + Ok(NodePlan::NibbledBranch { + partial: NibbleSlicePlan::new(partial, partial_padding), + value, + children, + }) + }, + NodeHeader::HashedValueLeaf(nibble_count) | NodeHeader::Leaf(nibble_count) => { + let padding = nibble_count % nibble_ops::NIBBLE_PER_BYTE != 0; + // check that the padding is valid (if any) + if padding && nibble_ops::pad_left(data[input.offset]) != 0 { + return Err(Error::BadFormat) + } + let partial = input.take( + (nibble_count + (nibble_ops::NIBBLE_PER_BYTE - 1)) / + nibble_ops::NIBBLE_PER_BYTE, + )?; + let partial_padding = nibble_ops::number_padding(nibble_count); + let value = if contains_hash { + ValuePlan::Node(input.take(H::LENGTH)?) + } else { + let count = >::decode(&mut input)?.0 as usize; + ValuePlan::Inline(input.take(count)?) + }; + + Ok(NodePlan::Leaf { + partial: NibbleSlicePlan::new(partial, partial_padding), + value, + }) + }, + } + } + + fn is_empty_node(data: &[u8]) -> bool { + data == ::empty_node() + } + + fn empty_node() -> &'static [u8] { + &[trie_constants::EMPTY_TRIE] + } + + fn leaf_node(partial: impl Iterator, number_nibble: usize, value: Value) -> Vec { + let contains_hash = matches!(&value, Value::Node(..)); + let mut output = if contains_hash { + partial_from_iterator_encode(partial, number_nibble, NodeKind::HashedValueLeaf) + } else { + partial_from_iterator_encode(partial, number_nibble, NodeKind::Leaf) + }; + match value { + Value::Inline(value) => { + Compact(value.len() as u32).encode_to(&mut output); + output.extend_from_slice(value); + }, + Value::Node(hash) => { + debug_assert!(hash.len() == H::LENGTH); + output.extend_from_slice(hash); + }, + } + output + } + + fn extension_node( + _partial: impl Iterator, + _nbnibble: usize, + _child: ChildReference<::Out>, + ) -> Vec { + unreachable!("No extension codec.") + } + + fn branch_node( + _children: impl Iterator::Out>>>>, + _maybe_value: Option, + ) -> Vec { + unreachable!("No extension codec.") + } + + fn branch_node_nibbled( + partial: impl Iterator, + number_nibble: usize, + children: impl Iterator::Out>>>>, + value: Option, + ) -> Vec { + let contains_hash = matches!(&value, Some(Value::Node(..))); + let mut output = match (&value, contains_hash) { + (&None, _) => + partial_from_iterator_encode(partial, number_nibble, NodeKind::BranchNoValue), + (_, false) => + partial_from_iterator_encode(partial, number_nibble, NodeKind::BranchWithValue), + (_, true) => + partial_from_iterator_encode(partial, number_nibble, NodeKind::HashedValueBranch), + }; + + let bitmap_index = output.len(); + let mut bitmap: [u8; BITMAP_LENGTH] = [0; BITMAP_LENGTH]; + (0..BITMAP_LENGTH).for_each(|_| output.push(0)); + match value { + Some(Value::Inline(value)) => { + Compact(value.len() as u32).encode_to(&mut output); + output.extend_from_slice(value); + }, + Some(Value::Node(hash)) => { + debug_assert!(hash.len() == H::LENGTH); + output.extend_from_slice(hash); + }, + None => (), + } + Bitmap::encode( + children.map(|maybe_child| match maybe_child.borrow() { + Some(ChildReference::Hash(h)) => { + h.as_ref().encode_to(&mut output); + true + }, + &Some(ChildReference::Inline(inline_data, len)) => { + inline_data.as_ref()[..len].encode_to(&mut output); + true + }, + None => false, + }), + bitmap.as_mut(), + ); + output[bitmap_index..bitmap_index + BITMAP_LENGTH] + .copy_from_slice(&bitmap[..BITMAP_LENGTH]); + output + } +} + +// utils + +/// Encode and allocate node type header (type and size), and partial value. +/// It uses an iterator over encoded partial bytes as input. +fn partial_from_iterator_encode>( + partial: I, + nibble_count: usize, + node_kind: NodeKind, +) -> Vec { + let mut output = Vec::with_capacity(4 + (nibble_count / nibble_ops::NIBBLE_PER_BYTE)); + match node_kind { + NodeKind::Leaf => NodeHeader::Leaf(nibble_count).encode_to(&mut output), + NodeKind::BranchWithValue => NodeHeader::Branch(true, nibble_count).encode_to(&mut output), + NodeKind::BranchNoValue => NodeHeader::Branch(false, nibble_count).encode_to(&mut output), + NodeKind::HashedValueLeaf => + NodeHeader::HashedValueLeaf(nibble_count).encode_to(&mut output), + NodeKind::HashedValueBranch => + NodeHeader::HashedValueBranch(nibble_count).encode_to(&mut output), + }; + output.extend(partial); + output +} + +const BITMAP_LENGTH: usize = 2; + +/// Radix 16 trie, bitmap encoding implementation, +/// it contains children mapping information for a branch +/// (children presence only), it encodes into +/// a compact bitmap encoding representation. +pub(crate) struct Bitmap(u16); + +impl Bitmap { + pub fn decode(data: &[u8]) -> Result { + let value = u16::decode(&mut &data[..])?; + if value == 0 { + Err("Bitmap without a child.".into()) + } else { + Ok(Bitmap(value)) + } + } + + pub fn value_at(&self, i: usize) -> bool { + self.0 & (1u16 << i) != 0 + } + + pub fn encode>(has_children: I, dest: &mut [u8]) { + let mut bitmap: u16 = 0; + let mut cursor: u16 = 1; + for v in has_children { + if v { + bitmap |= cursor + } + cursor <<= 1; + } + dest[0] = (bitmap % 256) as u8; + dest[1] = (bitmap / 256) as u8; + } +} + +/// substrate trie layout +pub struct LayoutV0(PhantomData); + +/// substrate trie layout, with external value nodes. +pub struct LayoutV1(PhantomData); + +impl TrieLayout for LayoutV0 +where + H: Hasher + core::fmt::Debug, +{ + const USE_EXTENSION: bool = false; + const ALLOW_EMPTY: bool = true; + const MAX_INLINE_VALUE: Option = None; + + type Hash = H; + type Codec = NodeCodec; +} + +impl TrieConfiguration for LayoutV0 +where + H: Hasher + core::fmt::Debug, +{ + fn trie_root(input: I) -> ::Out + where + I: IntoIterator, + A: AsRef<[u8]> + Ord, + B: AsRef<[u8]>, + { + trie_root::trie_root_no_extension::(input, Self::MAX_INLINE_VALUE) + } + + fn trie_root_unhashed(input: I) -> Vec + where + I: IntoIterator, + A: AsRef<[u8]> + Ord, + B: AsRef<[u8]>, + { + trie_root::unhashed_trie_no_extension::( + input, + Self::MAX_INLINE_VALUE, + ) + } + + fn encode_index(input: u32) -> Vec { + codec::Encode::encode(&codec::Compact(input)) + } +} + +impl TrieLayout for LayoutV1 +where + H: Hasher + core::fmt::Debug, +{ + const USE_EXTENSION: bool = false; + const ALLOW_EMPTY: bool = true; + const MAX_INLINE_VALUE: Option = Some(TRIE_VALUE_NODE_THRESHOLD); + + type Hash = H; + type Codec = NodeCodec; +} + +impl TrieConfiguration for LayoutV1 +where + H: Hasher + core::fmt::Debug, +{ + fn trie_root(input: I) -> ::Out + where + I: IntoIterator, + A: AsRef<[u8]> + Ord, + B: AsRef<[u8]>, + { + trie_root::trie_root_no_extension::(input, Self::MAX_INLINE_VALUE) + } + + fn trie_root_unhashed(input: I) -> Vec + where + I: IntoIterator, + A: AsRef<[u8]> + Ord, + B: AsRef<[u8]>, + { + trie_root::unhashed_trie_no_extension::( + input, + Self::MAX_INLINE_VALUE, + ) + } + + fn encode_index(input: u32) -> Vec { + codec::Encode::encode(&codec::Compact(input)) + } +} + +/// A node header +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub(crate) enum NodeHeader { + Null, + // contains wether there is a value and nibble count + Branch(bool, usize), + // contains nibble count + Leaf(usize), + // contains nibble count. + HashedValueBranch(usize), + // contains nibble count. + HashedValueLeaf(usize), +} + +impl NodeHeader { + pub(crate) fn contains_hash_of_value(&self) -> bool { + matches!(self, NodeHeader::HashedValueBranch(_) | NodeHeader::HashedValueLeaf(_)) + } +} + +/// NodeHeader without content +pub(crate) enum NodeKind { + Leaf, + BranchNoValue, + BranchWithValue, + HashedValueLeaf, + HashedValueBranch, +} + +impl Encode for NodeHeader { + fn encode_to(&self, output: &mut T) { + match self { + NodeHeader::Null => output.push_byte(trie_constants::EMPTY_TRIE), + NodeHeader::Branch(true, nibble_count) => + encode_size_and_prefix(*nibble_count, trie_constants::BRANCH_WITH_MASK, 2, output), + NodeHeader::Branch(false, nibble_count) => encode_size_and_prefix( + *nibble_count, + trie_constants::BRANCH_WITHOUT_MASK, + 2, + output, + ), + NodeHeader::Leaf(nibble_count) => + encode_size_and_prefix(*nibble_count, trie_constants::LEAF_PREFIX_MASK, 2, output), + NodeHeader::HashedValueBranch(nibble_count) => encode_size_and_prefix( + *nibble_count, + trie_constants::ALT_HASHING_BRANCH_WITH_MASK, + 4, + output, + ), + NodeHeader::HashedValueLeaf(nibble_count) => encode_size_and_prefix( + *nibble_count, + trie_constants::ALT_HASHING_LEAF_PREFIX_MASK, + 3, + output, + ), + } + } +} + +impl codec::EncodeLike for NodeHeader {} + +impl Decode for NodeHeader { + fn decode(input: &mut I) -> Result { + let i = input.read_byte()?; + if i == trie_constants::EMPTY_TRIE { + return Ok(NodeHeader::Null) + } + match i & (0b11 << 6) { + trie_constants::LEAF_PREFIX_MASK => Ok(NodeHeader::Leaf(decode_size(i, input, 2)?)), + trie_constants::BRANCH_WITH_MASK => + Ok(NodeHeader::Branch(true, decode_size(i, input, 2)?)), + trie_constants::BRANCH_WITHOUT_MASK => + Ok(NodeHeader::Branch(false, decode_size(i, input, 2)?)), + trie_constants::EMPTY_TRIE => { + if i & (0b111 << 5) == trie_constants::ALT_HASHING_LEAF_PREFIX_MASK { + Ok(NodeHeader::HashedValueLeaf(decode_size(i, input, 3)?)) + } else if i & (0b1111 << 4) == trie_constants::ALT_HASHING_BRANCH_WITH_MASK { + Ok(NodeHeader::HashedValueBranch(decode_size(i, input, 4)?)) + } else { + // do not allow any special encoding + Err("Unallowed encoding".into()) + } + }, + _ => unreachable!(), + } + } +} + +/// Returns an iterator over encoded bytes for node header and size. +/// Size encoding allows unlimited, length inefficient, representation, but +/// is bounded to 16 bit maximum value to avoid possible DOS. +pub(crate) fn size_and_prefix_iterator( + size: usize, + prefix: u8, + prefix_mask: usize, +) -> impl Iterator { + let max_value = 255u8 >> prefix_mask; + let l1 = core::cmp::min((max_value as usize).saturating_sub(1), size); + let (first_byte, mut rem) = if size == l1 { + (once(prefix + l1 as u8), 0) + } else { + (once(prefix + max_value), size - l1) + }; + let next_bytes = move || { + if rem > 0 { + if rem < 256 { + let result = rem - 1; + rem = 0; + Some(result as u8) + } else { + rem = rem.saturating_sub(255); + Some(255) + } + } else { + None + } + }; + first_byte.chain(core::iter::from_fn(next_bytes)) +} + +/// Encodes size and prefix to a stream output. +fn encode_size_and_prefix(size: usize, prefix: u8, prefix_mask: usize, out: &mut W) +where + W: Output + ?Sized, +{ + for b in size_and_prefix_iterator(size, prefix, prefix_mask) { + out.push_byte(b) + } +} + +/// Decode size only from stream input and header byte. +fn decode_size( + first: u8, + input: &mut impl Input, + prefix_mask: usize, +) -> Result { + let max_value = 255u8 >> prefix_mask; + let mut result = (first & max_value) as usize; + if result < max_value as usize { + return Ok(result) + } + result -= 1; + loop { + let n = input.read_byte()? as usize; + if n < 255 { + return Ok(result + n + 1) + } + result += 255; + } +} + +/// Error type used for trie related errors. +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum Error { + BadFormat, + Decode(codec::Error), + Trie(Box>), +} + +impl core::fmt::Display for Error { + fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result { + fmt.write_str("Error") + } +} + +impl std::error::Error for Error where H: core::fmt::Debug {} + +impl From for Error { + fn from(x: codec::Error) -> Self { + Error::Decode(x) + } +} + +impl From>> for Error { + fn from(x: Box>) -> Self { + Error::Trie(x) + } +} \ No newline at end of file diff --git a/src/lib/shared_utils/src/common/participant_crypto/merkle/mod.rs b/src/lib/shared_utils/src/common/participant_crypto/merkle/mod.rs new file mode 100644 index 00000000..87412a15 --- /dev/null +++ b/src/lib/shared_utils/src/common/participant_crypto/merkle/mod.rs @@ -0,0 +1,103 @@ + +use backing::ChildrenBacking; +use candid::Principal; +use hash256_std_hasher::Hash256StdHasher; +use hash_db::Hasher; +use serde::{Deserialize, Serialize}; +use trie_db::{proof::{generate_proof, verify_proof}, NodeCodec, TrieDBMut, TrieDBMutBuilder, TrieLayout, TrieMut}; + +use super::ProofOfChildren; + +mod layout; +mod backing; + +pub(super) type Hash = [u8; 32]; +pub(super) type ProofOfInclusion = Vec>; + +#[derive(Default, Debug, Clone, PartialEq)] +pub struct Blake3Hasher; + +impl Hasher for Blake3Hasher { + type Out = Hash; + + type StdHasher = Hash256StdHasher; + + const LENGTH: usize = 32; + + fn hash(x: &[u8]) -> Hash { + let mut hasher = blake3::Hasher::new(); + hasher.update(x); + let hs: [u8; 32] = hasher.finalize().into(); + hs + } +} + +pub(super) type ChildreenTreeLayout = layout::LayoutV1; + +#[derive(Serialize, Deserialize)] +pub struct ChildrenMerkle { + db: ChildrenBacking, + pub(super) root: Hash, + pub(super) proof_of_children: Option, +} + +impl Default for ChildrenMerkle { + fn default() -> Self { + Self { + db: ChildrenBacking::default(), + root: ::Codec::hashed_null_node(), + proof_of_children: None, + } + } +} + +impl ChildrenMerkle { + fn trie_mut(&mut self) -> TrieDBMut<'_, ChildreenTreeLayout> { + TrieDBMutBuilder::from_existing( + &mut self.db, + &mut self.root, + ).build() + } + + pub fn insert_children(&mut self, children: impl IntoIterator) { + let prev_root = self.root; + let mut trie = self.trie_mut(); + for child in children { + let key = Blake3Hasher::hash(child.as_slice()); + trie.insert(&key, b"_").expect("insertion should not fail"); + } + std::mem::drop(trie); + if self.root != prev_root { + // mark proof of children as stale + self.proof_of_children = None; + } + } + + pub fn remove_child(&mut self, child: Principal) { + let mut trie = self.trie_mut(); + let key = Blake3Hasher::hash(child.as_slice()); + trie.remove(&key).expect("removal should not fail"); + std::mem::drop(trie); + + // mark proof of children as stale + self.proof_of_children = None; + } + + pub fn proof_of_inclusion(&self, child: Principal) -> Result { + let key = Blake3Hasher::hash(child.as_slice()); + generate_proof::<_, ChildreenTreeLayout, _, _>( + &self.db, + &self.root, + [&key], + ).map_err(|e| format!("failed to generate proof of inclusion {e:?}")) + } + + pub fn verify_proof_of_inclusion(root: Hash, proof_of_inclusion: &[Vec], child: Principal) -> Result<(), String> { + let key = Blake3Hasher::hash(child.as_slice()); + verify_proof::( + &root, + proof_of_inclusion, + [&(key, Some(b"_"))] + ).map_err(|_| "invalid proof of inclusion".to_string()) + } +} diff --git a/src/lib/shared_utils/src/common/participant_crypto/mod.rs b/src/lib/shared_utils/src/common/participant_crypto/mod.rs index bda08a4b..2b105192 100644 --- a/src/lib/shared_utils/src/common/participant_crypto/mod.rs +++ b/src/lib/shared_utils/src/common/participant_crypto/mod.rs @@ -1,10 +1,12 @@ //! Utilities for creating and verifying proof that a given canister is a part of YRAL Backend canisters mod types; +pub mod merkle; use std::{cell::RefCell, collections::HashMap, thread::LocalKey}; use candid::{CandidType, Principal}; -use ed25519_dalek::{Signature, VerifyingKey, Verifier}; +use ed25519_compact::{Signature, PublicKey}; +use merkle::{ChildrenMerkle, ProofOfInclusion}; use types::{ManagementCanisterSchnorrPublicKeyReply, ManagementCanisterSchnorrPublicKeyRequest, ManagementCanisterSignatureReply, ManagementCanisterSignatureRequest, SchnorrAlgorithm, SchnorrKeyId}; use serde::{Serialize, Deserialize}; @@ -32,12 +34,12 @@ pub(crate) type LocalPoPStore = LocalKey>; pub struct PubKeyCache(HashMap>); impl PubKeyCache { - async fn get_or_init_public_key(store: &'static LocalPoPStore, principal: Principal) -> Result { + async fn get_or_init_public_key(store: &'static LocalPoPStore, principal: Principal) -> Result { let maybe_pk = store.with_borrow(|store| { store.pubkey_cache().0.get(&principal).cloned() }); if let Some(pk) = maybe_pk { - return VerifyingKey::try_from(pk.as_slice()) + return PublicKey::from_slice(pk.as_slice()) .map_err(|_| "invalid public key".to_string()) } @@ -60,7 +62,7 @@ impl PubKeyCache { })?; let key = key_res.public_key; - let vk = VerifyingKey::try_from(key.as_slice()) + let vk = PublicKey::from_slice(key.as_slice()) .map_err(|_| "invalid public key".to_string())?; store.with_borrow_mut(|store| { store.pubkey_cache_mut().0.insert(principal, key.clone()); @@ -73,14 +75,14 @@ impl PubKeyCache { #[derive(Serialize)] struct ProofOfAuthorityMsg { prefix: &'static [u8], - pub child: Principal, + pub merkle_root: [u8; 32], } impl ProofOfAuthorityMsg { - pub fn new(child: Principal) -> Self { + pub fn new(merkle_root: [u8; 32]) -> Self { Self { - prefix: b"CHILD", - child, + prefix: b"CHILDREN", + merkle_root, } } @@ -93,17 +95,16 @@ impl ProofOfAuthorityMsg { } } -/// Proof that this canister id is a child of the parent canister +/// Proof that a given merkle tree contains children of the parent canister #[derive(Clone, CandidType, Serialize, Deserialize)] -struct ProofOfChild { - // Principal of the child - principal: Principal, +struct ProofOfChildren { + merkle_root: [u8; 32], signature: Vec, } -impl ProofOfChild { - async fn new(child: Principal) -> Result { - let message = ProofOfAuthorityMsg::new(child); +impl ProofOfChildren { + async fn new(merkle_root: [u8; 32]) -> Result { + let message = ProofOfAuthorityMsg::new(merkle_root); let sign_args = ManagementCanisterSignatureRequest { message: message.serialize_cbor(), derivation_path: vec![], @@ -123,13 +124,13 @@ impl ProofOfChild { .map_err(|(_, msg)| format!("unable to sign: {msg}"))?; Ok(Self { - principal: child, + merkle_root, signature: sig_res.signature, }) } - pub fn verify(&self, parent_key: &VerifyingKey) -> Result<(), String> { - let message = ProofOfAuthorityMsg::new(self.principal); + pub fn verify(&self, parent_key: &PublicKey) -> Result<(), String> { + let message = ProofOfAuthorityMsg::new(self.merkle_root); let message_raw = message.serialize_cbor(); let sig = Signature::from_slice(&self.signature).map_err(|_| "invalid proof".to_string())?; @@ -140,6 +141,36 @@ impl ProofOfChild { } } +// Proof that given canister id exists in the merkle tree containing the children of the parent canister +#[derive(Clone, CandidType, Serialize, Deserialize)] +struct ProofOfChild { + principal: Principal, + children_proof: ProofOfChildren, + proof_of_inclusion: ProofOfInclusion, +} + +impl ProofOfChild { + pub fn new(children_proof: ProofOfChildren, principal: Principal, proof_of_inclusion: ProofOfInclusion) -> Self { + Self { + principal, + children_proof, + proof_of_inclusion, + } + } + + pub fn verify(&self, parent_key: &PublicKey) -> Result<(), String> { + self.children_proof.verify(parent_key)?; + + ChildrenMerkle::verify_proof_of_inclusion( + self.children_proof.merkle_root, + &self.proof_of_inclusion, + self.principal, + )?; + + Ok(()) + } +} + #[derive(Clone, CandidType, Serialize, Deserialize)] pub struct ProofOfParticipation { chain: Vec, @@ -153,12 +184,34 @@ impl ProofOfParticipation { } } - pub async fn derive_for_child(self, child: Principal) -> Result { - let mut chain = self.chain; - let proof = ProofOfChild::new(child).await?; - chain.push(proof); - Ok(Self { - chain, + pub async fn derive_for_child(&self, store: &'static LocalPoPStore, child: Principal) -> Result { + let (proof_of_inclusion, maybe_poc) = store.with_borrow(|s| { + let children_merkle = s.children_merkle(); + children_merkle.proof_of_inclusion(child) + .map(|poi| { + (poi, children_merkle.proof_of_children.clone()) + }) + })?; + let poc = if let Some(poc) = maybe_poc { + poc + } else { + let root = store.with_borrow(|s| s.children_merkle().root); + let poc = ProofOfChildren::new(root).await?; + store.with_borrow_mut(|s| { + s.children_merkle_mut().proof_of_children = Some(poc.clone()); + }); + poc + }; + + let mut chain = self.chain.clone(); + chain.push(ProofOfChild::new( + poc, + child, + proof_of_inclusion, + )); + + Ok(ProofOfParticipation { + chain }) } @@ -193,3 +246,9 @@ pub trait ProofOfParticipationStore { fn platform_orchestrator(&self) -> Principal; } + +pub trait ProofOfParticipationDeriverStore { + fn children_merkle(&self) -> &merkle::ChildrenMerkle; + + fn children_merkle_mut(&mut self) -> &mut merkle::ChildrenMerkle; +}