From d9cc7c7de7d4a6b3787edafa3788253fc9a86804 Mon Sep 17 00:00:00 2001 From: "Alisher A. Khassanov" Date: Tue, 8 Oct 2024 13:37:49 +0500 Subject: [PATCH] New `join_cluster` extrinsic (#425) ## Description New `join_cluster` extrinsic for the `pallet-ddc-clusters` which allows node provider to add a node to a cluster if it can pass `node_auth_smart_contract` authorization. It completes an unattended cluster extension flow implementation for the cluster manager allowing them to express cluster extension rules in form of smart contract requiring no further transactions from their account to add nodes to the cluster. ## Types of Changes Please select the branch type you are merging and fill in the relevant template. - [ ] Hotfix - [ ] Release - [x] Fix or Feature ## Fix or Feature ### Types of Changes - [ ] Tech Debt (Code improvements) - [ ] Bug fix (non-breaking change which fixes an issue) - [x] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [ ] Dependency upgrade (A change in substrate or any 3rd party crate version) ### Checklist for Fix or Feature - [x] Change has been tested locally. - [x] Change adds / updates tests if applicable. - [x] Changelog doc updated. - [x] `spec_version` has been incremented. - [ ] `network-relayer`'s [events](https://github.com/Cerebellum-Network/network-relayer/blob/dev-cere/shared/substrate/events.go) have been updated according to the blockchain events if applicable. - [x] All CI checks have been passed successfully --- .github/workflows/ci.yaml | 2 +- CHANGELOG.md | 1 + pallets/ddc-clusters/src/benchmarking.rs | 13 + pallets/ddc-clusters/src/lib.rs | 104 +- .../ddc-clusters/src/node_provider_auth.rs | 2 +- .../ddc-clusters/src/test_data/metadata.json | 904 ++++++++++++------ .../node_provider_auth_white_list.wasm | Bin 4489 -> 4260 bytes pallets/ddc-clusters/src/tests.rs | 105 +- pallets/ddc-clusters/src/weights.rs | 69 +- runtime/cere-dev/src/lib.rs | 2 +- runtime/cere/src/lib.rs | 2 +- 11 files changed, 847 insertions(+), 357 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 2830e4aa1..db3ee2780 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -50,7 +50,7 @@ jobs: - name: Rust Cache uses: Swatinem/rust-cache@v2 - name: Install try-runtime - run: cargo install --git https://github.com/paritytech/try-runtime-cli --locked + run: cargo install --git https://github.com/paritytech/try-runtime-cli --tag v0.7.0 --locked - name: Check Build run: | cargo build --release --features try-runtime diff --git a/CHANGELOG.md b/CHANGELOG.md index df784a084..66a68e691 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - [C,D] `pallet-ddc-verification`: Introduction of the Verification pallet to ensure the secure posting and retrieval of verification keys to and from the blockchain. +- [C,D] `pallet-ddc-clusters`: New `join_cluster` extrinsic. ## [5.4.0] diff --git a/pallets/ddc-clusters/src/benchmarking.rs b/pallets/ddc-clusters/src/benchmarking.rs index e6728ba74..9e4a61fd4 100644 --- a/pallets/ddc-clusters/src/benchmarking.rs +++ b/pallets/ddc-clusters/src/benchmarking.rs @@ -66,6 +66,19 @@ benchmarks! { assert!(ClustersNodes::::contains_key(cluster_id, node_pub_key)); } + join_cluster { + let bytes = [0u8; 32]; + let node_pub_key = NodePubKey::StoragePubKey(AccountId32::from(bytes)); + let cluster_id = ClusterId::from([1; 20]); + let user = account::("user", USER_SEED, 0u32); + let balance = ::Currency::minimum_balance() * 1_000_000u32.into(); + let _ = ::Currency::make_free_balance_be(&user, balance); + let _ = config_cluster_and_node::(user.clone(), node_pub_key.clone(), cluster_id); + }: _(RawOrigin::Signed(user.clone()), cluster_id, node_pub_key.clone()) + verify { + assert!(ClustersNodes::::contains_key(cluster_id, node_pub_key)); + } + remove_node { let bytes = [0u8; 32]; let node_pub_key = NodePubKey::StoragePubKey(AccountId32::from(bytes)); diff --git a/pallets/ddc-clusters/src/lib.rs b/pallets/ddc-clusters/src/lib.rs index 0f7b149d1..78ecb63cf 100644 --- a/pallets/ddc-clusters/src/lib.rs +++ b/pallets/ddc-clusters/src/lib.rs @@ -119,6 +119,7 @@ pub mod pallet { AttemptToRemoveNonExistentNode, AttemptToRemoveNotAssignedNode, OnlyClusterManager, + OnlyNodeProvider, NodeIsNotAuthorized, NodeHasNoActivatedStake, NodeStakeIsInvalid, @@ -311,23 +312,9 @@ pub mod pallet { ensure!(!has_chilling_attempt, Error::::NodeChillingIsProhibited); // Node with this node with this public key exists. - let node = T::NodeRepository::get(node_pub_key.clone()) + T::NodeRepository::get(node_pub_key.clone()) .map_err(|_| Error::::AttemptToAddNonExistentNode)?; - // Cluster extension smart contract allows joining. - if let Some(address) = cluster.props.node_provider_auth_contract.clone() { - let auth_contract = NodeProviderAuthContract::::new(address, caller_id); - - let is_authorized = auth_contract - .is_authorized( - node.get_provider_id().to_owned(), - node.get_pub_key(), - node.get_type(), - ) - .map_err(Into::>::into)?; - ensure!(is_authorized, Error::::NodeIsNotAuthorized); - }; - Self::do_add_node(cluster, node_pub_key, node_kind) } @@ -394,6 +381,55 @@ pub mod pallet { Self::do_validate_node(cluster_id, node_pub_key, succeeded) } + + #[pallet::call_index(5)] + #[pallet::weight(::WeightInfo::join_cluster())] + pub fn join_cluster( + origin: OriginFor, + cluster_id: ClusterId, + node_pub_key: NodePubKey, + ) -> DispatchResult { + let caller_id = ensure_signed(origin)?; + + // Cluster with a given id exists and has an auth smart contract. + let cluster = + Clusters::::try_get(cluster_id).map_err(|_| Error::::ClusterDoesNotExist)?; + let node_provider_auth_contract_address = cluster + .props + .node_provider_auth_contract + .clone() + .ok_or(Error::::NodeIsNotAuthorized)?; + + // Node with this public key exists and belongs to the caller. + let node = T::NodeRepository::get(node_pub_key.clone()) + .map_err(|_| Error::::AttemptToAddNonExistentNode)?; + ensure!(*node.get_provider_id() == caller_id, Error::::OnlyNodeProvider); + + // Sufficient funds are locked at the DDC Staking module. + let has_activated_stake = + T::StakingVisitor::has_activated_stake(&node_pub_key, &cluster_id) + .map_err(Into::>::into)?; + ensure!(has_activated_stake, Error::::NodeHasNoActivatedStake); + + // Candidate is not planning to pause operations any time soon. + let has_chilling_attempt = T::StakingVisitor::has_chilling_attempt(&node_pub_key) + .map_err(Into::>::into)?; + ensure!(!has_chilling_attempt, Error::::NodeChillingIsProhibited); + + // Cluster auth smart contract allows joining. + let auth_contract = + NodeProviderAuthContract::::new(node_provider_auth_contract_address, caller_id); + let is_authorized = auth_contract + .is_authorized( + node.get_provider_id().to_owned(), + node.get_pub_key(), + node.get_type(), + ) + .map_err(Into::>::into)?; + ensure!(is_authorized, Error::::NodeIsNotAuthorized); + + Self::do_join_cluster(cluster, node_pub_key) + } } impl Pallet { @@ -538,6 +574,44 @@ pub mod pallet { Ok(()) } + fn do_join_cluster( + cluster: Cluster, + node_pub_key: NodePubKey, + ) -> DispatchResult { + ensure!(cluster.can_manage_nodes(), Error::::UnexpectedClusterStatus); + + let mut node: pallet_ddc_nodes::Node = T::NodeRepository::get(node_pub_key.clone()) + .map_err(|_| Error::::AttemptToAddNonExistentNode)?; + ensure!(node.get_cluster_id().is_none(), Error::::AttemptToAddAlreadyAssignedNode); + + node.set_cluster_id(Some(cluster.cluster_id)); + T::NodeRepository::update(node).map_err(|_| Error::::AttemptToAddNonExistentNode)?; + + ClustersNodes::::insert( + cluster.cluster_id, + node_pub_key.clone(), + ClusterNodeState { + kind: ClusterNodeKind::External, + status: ClusterNodeStatus::ValidationSucceeded, + added_at: frame_system::Pallet::::block_number(), + }, + ); + Self::deposit_event(Event::::ClusterNodeAdded { + cluster_id: cluster.cluster_id, + node_pub_key, + }); + + let mut current_stats = ClustersNodesStats::::try_get(cluster.cluster_id) + .map_err(|_| Error::::ClusterDoesNotExist)?; + current_stats.validation_succeeded = current_stats + .validation_succeeded + .checked_add(1) + .ok_or(Error::::ArithmeticOverflow)?; + ClustersNodesStats::::insert(cluster.cluster_id, current_stats); + + Ok(()) + } + fn do_remove_node( cluster: Cluster, node_pub_key: NodePubKey, diff --git a/pallets/ddc-clusters/src/node_provider_auth.rs b/pallets/ddc-clusters/src/node_provider_auth.rs index 1e9dee165..6f293e36f 100644 --- a/pallets/ddc-clusters/src/node_provider_auth.rs +++ b/pallets/ddc-clusters/src/node_provider_auth.rs @@ -63,7 +63,7 @@ where .result .map_err(|_| NodeProviderAuthContractError::ContractCallFailed)? .data - .first() + .get(1) .is_some_and(|x| *x == 1); Ok(is_authorized) diff --git a/pallets/ddc-clusters/src/test_data/metadata.json b/pallets/ddc-clusters/src/test_data/metadata.json index 4ffe526d6..f05b952e2 100644 --- a/pallets/ddc-clusters/src/test_data/metadata.json +++ b/pallets/ddc-clusters/src/test_data/metadata.json @@ -1,8 +1,17 @@ { "source": { - "hash": "0x26d3338336a945f457e19992f37947659b0b7e4b2962369fe2f97ca7ebb95a09", - "language": "ink! 3.4.0", - "compiler": "rustc 1.69.0-nightly" + "hash": "0xa41834e9d696d32cbc6ba6e83d7e904ad494bf41016f41d9e0186979de6965c3", + "language": "ink! 4.3.0", + "compiler": "rustc 1.76.0", + "build_info": { + "build_mode": "Release", + "cargo_contract_version": "2.2.1", + "rust_toolchain": "stable-x86_64-unknown-linux-gnu", + "wasm_opt_settings": { + "keep_debug_symbols": false, + "optimization_passes": "Z" + } + } }, "contract": { "name": "node_provider_auth_white_list", @@ -13,353 +22,620 @@ "description": "Node provider authorization layer based on admin approval", "license": "Apache-2.0" }, - "V3": { - "spec": { - "constructors": [ - { - "args": [], - "docs": [], - "label": "new", - "payable": false, - "selector": "0x9bae9d5e" - } - ], - "docs": [], - "events": [], - "messages": [ - { - "args": [ - { - "label": "_node_provider", - "type": { - "displayName": [ - "AccountId" - ], - "type": 0 - } - }, - { - "label": "node_pub_key", - "type": { - "displayName": [ - "NodePubKey" - ], - "type": 4 - } - }, - { - "label": "_node_variant", - "type": { - "displayName": [ - "NodeType" - ], - "type": 2 - } - } + "spec": { + "constructors": [ + { + "args": [], + "default": false, + "docs": [], + "label": "new", + "payable": false, + "returnType": { + "displayName": [ + "ink_primitives", + "ConstructorResult" ], - "docs": [], - "label": "is_authorized", - "mutates": false, - "payable": false, - "returnType": { - "displayName": [ - "bool" - ], - "type": 5 - }, - "selector": "0x96b0453e" + "type": 4 }, - { - "args": [ - { - "label": "node_pub_key", - "type": { - "displayName": [ - "NodePubKey" - ], - "type": 4 - } + "selector": "0x9bae9d5e" + } + ], + "docs": [], + "environment": { + "accountId": { + "displayName": [ + "AccountId" + ], + "type": 0 + }, + "balance": { + "displayName": [ + "Balance" + ], + "type": 13 + }, + "blockNumber": { + "displayName": [ + "BlockNumber" + ], + "type": 16 + }, + "chainExtension": { + "displayName": [ + "ChainExtension" + ], + "type": 17 + }, + "hash": { + "displayName": [ + "Hash" + ], + "type": 14 + }, + "maxEventTopics": 4, + "timestamp": { + "displayName": [ + "Timestamp" + ], + "type": 15 + } + }, + "events": [], + "lang_error": { + "displayName": [ + "ink", + "LangError" + ], + "type": 6 + }, + "messages": [ + { + "args": [ + { + "label": "_node_provider", + "type": { + "displayName": [ + "AccountId" + ], + "type": 0 } - ], - "docs": [], - "label": "add_node_pub_key", - "mutates": true, - "payable": false, - "returnType": { - "displayName": [ - "Result" - ], - "type": 7 }, - "selector": "0x7a04093d" - }, - { - "args": [ - { - "label": "node_pub_key", - "type": { - "displayName": [ - "NodePubKey" - ], - "type": 4 - } + { + "label": "node_pub_key", + "type": { + "displayName": [ + "NodePubKey" + ], + "type": 7 } - ], - "docs": [], - "label": "remove_node_pub_key", - "mutates": true, - "payable": false, - "returnType": { - "displayName": [ - "Result" - ], - "type": 7 }, - "selector": "0xed3668b6" - }, - { - "args": [ - { - "label": "node_pub_key", - "type": { - "displayName": [ - "NodePubKey" - ], - "type": 4 - } + { + "label": "_node_variant", + "type": { + "displayName": [ + "NodeType" + ], + "type": 2 } + } + ], + "default": false, + "docs": [], + "label": "is_authorized", + "mutates": false, + "payable": false, + "returnType": { + "displayName": [ + "ink", + "MessageResult" ], - "docs": [], - "label": "has_node_pub_key", - "mutates": false, - "payable": false, - "returnType": { - "displayName": [ - "bool" - ], - "type": 5 - }, - "selector": "0x9b868ecf" + "type": 8 }, - { - "args": [], - "docs": [], - "label": "get_admin", - "mutates": false, - "payable": false, - "returnType": { - "displayName": [ - "AccountId" - ], - "type": 0 - }, - "selector": "0x57b8a8a7" - } - ] - }, - "storage": { - "struct": { - "fields": [ - { - "layout": { - "cell": { - "key": "0x0000000000000000000000000000000000000000000000000000000000000000", - "ty": 0 - } - }, - "name": "admin" - }, - { - "layout": { - "cell": { - "key": "0x0100000000000000000000000000000000000000000000000000000000000000", - "ty": 3 - } - }, - "name": "list" - } - ] - } - }, - "types": [ + "selector": "0x96b0453e" + }, { - "id": 0, - "type": { - "def": { - "composite": { - "fields": [ - { - "type": 1, - "typeName": "[u8; 32]" - } - ] + "args": [ + { + "label": "node_pub_key", + "type": { + "displayName": [ + "NodePubKey" + ], + "type": 7 } - }, - "path": [ - "ink_env", - "types", - "AccountId" - ] - } + } + ], + "default": false, + "docs": [], + "label": "add_node_pub_key", + "mutates": true, + "payable": false, + "returnType": { + "displayName": [ + "ink", + "MessageResult" + ], + "type": 9 + }, + "selector": "0x7a04093d" }, { - "id": 1, - "type": { - "def": { - "array": { - "len": 32, - "type": 2 + "args": [ + { + "label": "node_pub_key", + "type": { + "displayName": [ + "NodePubKey" + ], + "type": 7 } } - } + ], + "default": false, + "docs": [], + "label": "remove_node_pub_key", + "mutates": true, + "payable": false, + "returnType": { + "displayName": [ + "ink", + "MessageResult" + ], + "type": 9 + }, + "selector": "0xed3668b6" }, { - "id": 2, - "type": { - "def": { - "primitive": "u8" + "args": [ + { + "label": "node_pub_key", + "type": { + "displayName": [ + "NodePubKey" + ], + "type": 7 + } } - } + ], + "default": false, + "docs": [], + "label": "has_node_pub_key", + "mutates": false, + "payable": false, + "returnType": { + "displayName": [ + "ink", + "MessageResult" + ], + "type": 8 + }, + "selector": "0x9b868ecf" }, { - "id": 3, - "type": { - "def": { - "composite": { - "fields": [ - { - "name": "offset_key", - "type": 6, - "typeName": "Key" - } - ] - } - }, - "params": [ + "args": [], + "default": false, + "docs": [], + "label": "get_admin", + "mutates": false, + "payable": false, + "returnType": { + "displayName": [ + "ink", + "MessageResult" + ], + "type": 12 + }, + "selector": "0x57b8a8a7" + } + ] + }, + "storage": { + "root": { + "layout": { + "struct": { + "fields": [ { - "name": "K", - "type": 4 + "layout": { + "leaf": { + "key": "0x00000000", + "ty": 0 + } + }, + "name": "admin" }, { - "name": "V", - "type": 5 + "layout": { + "root": { + "layout": { + "leaf": { + "key": "0x8e4a1f5b", + "ty": 3 + } + }, + "root_key": "0x8e4a1f5b" + } + }, + "name": "list" } ], - "path": [ - "ink_storage", - "lazy", - "mapping", - "Mapping" - ] + "name": "WhiteListAuthContract" } }, - { - "id": 4, - "type": { - "def": { - "sequence": { - "type": 2 - } + "root_key": "0x00000000" + } + }, + "types": [ + { + "id": 0, + "type": { + "def": { + "composite": { + "fields": [ + { + "type": 1, + "typeName": "[u8; 32]" + } + ] } - } - }, - { - "id": 5, - "type": { - "def": { - "primitive": "bool" + }, + "path": [ + "ink_primitives", + "types", + "AccountId" + ] + } + }, + { + "id": 1, + "type": { + "def": { + "array": { + "len": 32, + "type": 2 } } - }, - { - "id": 6, - "type": { - "def": { - "composite": { - "fields": [ - { - "type": 1, - "typeName": "[u8; 32]" - } - ] - } - }, - "path": [ - "ink_primitives", - "Key" - ] + } + }, + { + "id": 2, + "type": { + "def": { + "primitive": "u8" } - }, - { - "id": 7, - "type": { - "def": { - "variant": { - "variants": [ - { - "fields": [ - { - "type": 8 - } - ], - "index": 0, - "name": "Ok" - }, - { - "fields": [ - { - "type": 9 - } - ], - "index": 1, - "name": "Err" - } - ] - } + } + }, + { + "id": 3, + "type": { + "def": { + "primitive": "bool" + } + } + }, + { + "id": 4, + "type": { + "def": { + "variant": { + "variants": [ + { + "fields": [ + { + "type": 5 + } + ], + "index": 0, + "name": "Ok" + }, + { + "fields": [ + { + "type": 6 + } + ], + "index": 1, + "name": "Err" + } + ] + } + }, + "params": [ + { + "name": "T", + "type": 5 }, - "params": [ - { - "name": "T", - "type": 8 - }, - { - "name": "E", - "type": 9 - } - ], - "path": [ - "Result" - ] + { + "name": "E", + "type": 6 + } + ], + "path": [ + "Result" + ] + } + }, + { + "id": 5, + "type": { + "def": { + "tuple": [] } - }, - { - "id": 8, - "type": { - "def": { - "tuple": [] + } + }, + { + "id": 6, + "type": { + "def": { + "variant": { + "variants": [ + { + "index": 1, + "name": "CouldNotReadInput" + } + ] + } + }, + "path": [ + "ink_primitives", + "LangError" + ] + } + }, + { + "id": 7, + "type": { + "def": { + "sequence": { + "type": 2 } } - }, - { - "id": 9, - "type": { - "def": { - "variant": { - "variants": [ - { - "index": 0, - "name": "OnlyAdmin" - } - ] - } + } + }, + { + "id": 8, + "type": { + "def": { + "variant": { + "variants": [ + { + "fields": [ + { + "type": 3 + } + ], + "index": 0, + "name": "Ok" + }, + { + "fields": [ + { + "type": 6 + } + ], + "index": 1, + "name": "Err" + } + ] + } + }, + "params": [ + { + "name": "T", + "type": 3 }, - "path": [ - "node_provider_auth_white_list", - "node_provider_auth_white_list", - "Error" - ] + { + "name": "E", + "type": 6 + } + ], + "path": [ + "Result" + ] + } + }, + { + "id": 9, + "type": { + "def": { + "variant": { + "variants": [ + { + "fields": [ + { + "type": 10 + } + ], + "index": 0, + "name": "Ok" + }, + { + "fields": [ + { + "type": 6 + } + ], + "index": 1, + "name": "Err" + } + ] + } + }, + "params": [ + { + "name": "T", + "type": 10 + }, + { + "name": "E", + "type": 6 + } + ], + "path": [ + "Result" + ] + } + }, + { + "id": 10, + "type": { + "def": { + "variant": { + "variants": [ + { + "fields": [ + { + "type": 5 + } + ], + "index": 0, + "name": "Ok" + }, + { + "fields": [ + { + "type": 11 + } + ], + "index": 1, + "name": "Err" + } + ] + } + }, + "params": [ + { + "name": "T", + "type": 5 + }, + { + "name": "E", + "type": 11 + } + ], + "path": [ + "Result" + ] + } + }, + { + "id": 11, + "type": { + "def": { + "variant": { + "variants": [ + { + "index": 0, + "name": "OnlyAdmin" + } + ] + } + }, + "path": [ + "node_provider_auth_white_list", + "node_provider_auth_white_list", + "Error" + ] + } + }, + { + "id": 12, + "type": { + "def": { + "variant": { + "variants": [ + { + "fields": [ + { + "type": 0 + } + ], + "index": 0, + "name": "Ok" + }, + { + "fields": [ + { + "type": 6 + } + ], + "index": 1, + "name": "Err" + } + ] + } + }, + "params": [ + { + "name": "T", + "type": 0 + }, + { + "name": "E", + "type": 6 + } + ], + "path": [ + "Result" + ] + } + }, + { + "id": 13, + "type": { + "def": { + "primitive": "u128" } } - ] - } + }, + { + "id": 14, + "type": { + "def": { + "composite": { + "fields": [ + { + "type": 1, + "typeName": "[u8; 32]" + } + ] + } + }, + "path": [ + "ink_primitives", + "types", + "Hash" + ] + } + }, + { + "id": 15, + "type": { + "def": { + "primitive": "u64" + } + } + }, + { + "id": 16, + "type": { + "def": { + "primitive": "u32" + } + } + }, + { + "id": 17, + "type": { + "def": { + "variant": {} + }, + "path": [ + "ink_env", + "types", + "NoChainExtension" + ] + } + } + ], + "version": "4" } \ No newline at end of file diff --git a/pallets/ddc-clusters/src/test_data/node_provider_auth_white_list.wasm b/pallets/ddc-clusters/src/test_data/node_provider_auth_white_list.wasm index 39d305f7f09c5124a444c92f60f9e84e42604e4a..5c3394495dcedae3663284d74fc515e3382a0356 100644 GIT binary patch literal 4260 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::ClusterDoesNotExist ); + assert_noop!( + DdcClusters::join_cluster( + RuntimeOrigin::signed(cluster_manager_id.clone()), + cluster_id, + NodePubKey::StoragePubKey(node_pub_key.clone()), + ), + Error::::ClusterDoesNotExist + ); // Creating 1 cluster should work fine assert_ok!(DdcClusters::create_cluster( @@ -154,7 +163,7 @@ fn add_and_delete_node_works() { cluster_id, cluster_reserve_id.clone(), ClusterParams { - node_provider_auth_contract: Some(cluster_manager_id.clone()), + node_provider_auth_contract: None, erasure_coding_required: 4, erasure_coding_total: 6, replication_total: 3 @@ -173,6 +182,28 @@ fn add_and_delete_node_works() { } )); + // Cluster has no auth smart contract + assert_noop!( + DdcClusters::join_cluster( + RuntimeOrigin::signed(cluster_manager_id.clone()), + cluster_id, + NodePubKey::StoragePubKey(node_pub_key.clone()), + ), + Error::::NodeIsNotAuthorized + ); + + // Set an incorrect address for auth contract + assert_ok!(DdcClusters::set_cluster_params( + RuntimeOrigin::signed(cluster_manager_id.clone()), + cluster_id, + ClusterParams { + node_provider_auth_contract: Some(cluster_manager_id.clone()), + erasure_coding_required: 4, + erasure_coding_total: 6, + replication_total: 3 + }, + )); + // Not Cluster Manager assert_noop!( DdcClusters::add_node( @@ -194,6 +225,14 @@ fn add_and_delete_node_works() { ), Error::::AttemptToAddNonExistentNode ); + assert_noop!( + DdcClusters::join_cluster( + RuntimeOrigin::signed(cluster_manager_id.clone()), + cluster_id, + NodePubKey::StoragePubKey(node_pub_key.clone()), + ), + Error::::AttemptToAddNonExistentNode + ); let storage_node_params = StorageNodeParams { mode: StorageNodeMode::Storage, @@ -209,18 +248,17 @@ fn add_and_delete_node_works() { assert_ok!(DdcNodes::create_node( RuntimeOrigin::signed(cluster_manager_id.clone()), NodePubKey::StoragePubKey(node_pub_key.clone()), - NodeParams::StorageParams(storage_node_params) + NodeParams::StorageParams(storage_node_params.clone()), )); - // Node doesn't exist + // Not node provider assert_noop!( - DdcClusters::add_node( - RuntimeOrigin::signed(cluster_manager_id.clone()), + DdcClusters::join_cluster( + RuntimeOrigin::signed(node_pub_key.clone()), cluster_id, NodePubKey::StoragePubKey(node_pub_key.clone()), - ClusterNodeKind::Genesis ), - Error::::NodeAuthContractCallFailed + Error::::OnlyNodeProvider ); // Set the correct address for auth contract @@ -237,7 +275,22 @@ fn add_and_delete_node_works() { assert_ok!(DdcClusters::bond_cluster(&cluster_id)); - // Node added succesfully + // Node is not authorized to join + assert_ok!(DdcNodes::create_node( + RuntimeOrigin::signed(cluster_manager_id.clone()), + NodePubKey::StoragePubKey(node_pub_key2.clone()), + NodeParams::StorageParams(storage_node_params.clone()), + )); + assert_noop!( + DdcClusters::join_cluster( + RuntimeOrigin::signed(cluster_manager_id.clone()), + cluster_id, + NodePubKey::StoragePubKey(node_pub_key2.clone()), + ), + Error::::NodeIsNotAuthorized + ); + + // Node added successfully assert_ok!(DdcClusters::add_node( RuntimeOrigin::signed(cluster_manager_id.clone()), cluster_id, @@ -261,6 +314,14 @@ fn add_and_delete_node_works() { ), Error::::AttemptToAddAlreadyAssignedNode ); + assert_noop!( + DdcClusters::join_cluster( + RuntimeOrigin::signed(cluster_manager_id.clone()), + cluster_id, + NodePubKey::StoragePubKey(node_pub_key.clone()), + ), + Error::::AttemptToAddAlreadyAssignedNode + ); // Checking that event was emitted System::assert_last_event( @@ -290,13 +351,35 @@ fn add_and_delete_node_works() { // Remove node should fail assert_noop!( DdcClusters::remove_node( - RuntimeOrigin::signed(cluster_manager_id), + RuntimeOrigin::signed(cluster_manager_id.clone()), cluster_id, - NodePubKey::StoragePubKey(node_pub_key), + NodePubKey::StoragePubKey(node_pub_key.clone()), ), Error::::AttemptToRemoveNotAssignedNode ); + // Node joined successfully + assert_ok!(DdcClusters::join_cluster( + RuntimeOrigin::signed(cluster_manager_id.clone()), + cluster_id, + NodePubKey::StoragePubKey(node_pub_key.clone()), + )); + + assert!(>::contains_node( + &cluster_id, + &NodePubKey::StoragePubKey(node_pub_key.clone()), + None + )); + + // Checking that event was emitted + System::assert_last_event( + Event::ClusterNodeAdded { + cluster_id, + node_pub_key: NodePubKey::StoragePubKey(node_pub_key.clone()), + } + .into(), + ); + pub const CTOR_SELECTOR: [u8; 4] = hex!("9bae9d5e"); fn encode_constructor() -> Vec { diff --git a/pallets/ddc-clusters/src/weights.rs b/pallets/ddc-clusters/src/weights.rs index df5c5957e..83ce438dc 100644 --- a/pallets/ddc-clusters/src/weights.rs +++ b/pallets/ddc-clusters/src/weights.rs @@ -1,7 +1,7 @@ //! Autogenerated weights for pallet_ddc_clusters //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2024-07-05, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-10-08, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! HOSTNAME: `bench`, CPU: `AMD EPYC-Milan Processor` //! EXECUTION: , WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 @@ -30,6 +30,7 @@ use sp_std::marker::PhantomData; pub trait WeightInfo { fn create_cluster() -> Weight; fn add_node() -> Weight; + fn join_cluster() -> Weight; fn remove_node() -> Weight; fn set_cluster_params() -> Weight; fn validate_node() -> Weight; @@ -61,6 +62,27 @@ impl WeightInfo for SubstrateWeight { // Proof: `DdcStaking::Ledger` (`max_values`: None, `max_size`: None, mode: `Measured`) // Storage: `DdcNodes::StorageNodes` (r:1 w:1) // Proof: `DdcNodes::StorageNodes` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `DdcClusters::ClustersNodesStats` (r:1 w:1) + // Proof: `DdcClusters::ClustersNodesStats` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `DdcClusters::ClustersNodes` (r:0 w:1) + // Proof: `DdcClusters::ClustersNodes` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn add_node() -> Weight { + Weight::from_parts(82_055_000_u64, 0) + .saturating_add(T::DbWeight::get().reads(7_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + } + // Storage: `DdcClusters::Clusters` (r:1 w:0) + // Proof: `DdcClusters::Clusters` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `DdcNodes::StorageNodes` (r:1 w:1) + // Proof: `DdcNodes::StorageNodes` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `DdcStaking::Nodes` (r:1 w:0) + // Proof: `DdcStaking::Nodes` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `DdcStaking::Storages` (r:1 w:0) + // Proof: `DdcStaking::Storages` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `DdcStaking::Bonded` (r:1 w:0) + // Proof: `DdcStaking::Bonded` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `DdcStaking::Ledger` (r:1 w:0) + // Proof: `DdcStaking::Ledger` (`max_values`: None, `max_size`: None, mode: `Measured`) // Storage: `Contracts::MigrationInProgress` (r:1 w:0) // Proof: `Contracts::MigrationInProgress` (`max_values`: Some(1), `max_size`: Some(1026), added: 1521, mode: `MaxEncodedLen`) // Storage: `System::Account` (r:1 w:0) @@ -79,12 +101,12 @@ impl WeightInfo for SubstrateWeight { // Proof: `DdcClusters::ClustersNodesStats` (`max_values`: None, `max_size`: None, mode: `Measured`) // Storage: `DdcClusters::ClustersNodes` (r:0 w:1) // Proof: `DdcClusters::ClustersNodes` (`max_values`: None, `max_size`: None, mode: `Measured`) - // Storage: UNKNOWN KEY `0x89eb0d6a8a691dae2cd15ed0369931ce0a949ecafa5c3f93f8121833646e15c3` (r:1 w:0) - // Proof: UNKNOWN KEY `0x89eb0d6a8a691dae2cd15ed0369931ce0a949ecafa5c3f93f8121833646e15c3` (r:1 w:0) - // Storage: UNKNOWN KEY `0xc3ad1d87683b6ac25f2e809346840d7a7ed0c05653ee606dba68aba3bdb5d957` (r:1 w:0) - // Proof: UNKNOWN KEY `0xc3ad1d87683b6ac25f2e809346840d7a7ed0c05653ee606dba68aba3bdb5d957` (r:1 w:0) - fn add_node() -> Weight { - Weight::from_parts(655_639_000_u64, 0) + // Storage: UNKNOWN KEY `0x11d2df4e979aa105cf552e9544ebd2b500000000` (r:1 w:0) + // Proof: UNKNOWN KEY `0x11d2df4e979aa105cf552e9544ebd2b500000000` (r:1 w:0) + // Storage: UNKNOWN KEY `0xee61cd03857d4d6515cbe7367c56239d5b1f4a8e800000000000000000000000` (r:1 w:0) + // Proof: UNKNOWN KEY `0xee61cd03857d4d6515cbe7367c56239d5b1f4a8e800000000000000000000000` (r:1 w:0) + fn join_cluster() -> Weight { + Weight::from_parts(656_290_000_u64, 0) .saturating_add(T::DbWeight::get().reads(17_u64)) .saturating_add(T::DbWeight::get().writes(6_u64)) } @@ -146,6 +168,27 @@ impl WeightInfo for () { // Proof: `DdcStaking::Ledger` (`max_values`: None, `max_size`: None, mode: `Measured`) // Storage: `DdcNodes::StorageNodes` (r:1 w:1) // Proof: `DdcNodes::StorageNodes` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `DdcClusters::ClustersNodesStats` (r:1 w:1) + // Proof: `DdcClusters::ClustersNodesStats` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `DdcClusters::ClustersNodes` (r:0 w:1) + // Proof: `DdcClusters::ClustersNodes` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn add_node() -> Weight { + Weight::from_parts(82_055_000_u64, 0) + .saturating_add(RocksDbWeight::get().reads(7_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + } + // Storage: `DdcClusters::Clusters` (r:1 w:0) + // Proof: `DdcClusters::Clusters` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `DdcNodes::StorageNodes` (r:1 w:1) + // Proof: `DdcNodes::StorageNodes` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `DdcStaking::Nodes` (r:1 w:0) + // Proof: `DdcStaking::Nodes` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `DdcStaking::Storages` (r:1 w:0) + // Proof: `DdcStaking::Storages` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `DdcStaking::Bonded` (r:1 w:0) + // Proof: `DdcStaking::Bonded` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `DdcStaking::Ledger` (r:1 w:0) + // Proof: `DdcStaking::Ledger` (`max_values`: None, `max_size`: None, mode: `Measured`) // Storage: `Contracts::MigrationInProgress` (r:1 w:0) // Proof: `Contracts::MigrationInProgress` (`max_values`: Some(1), `max_size`: Some(1026), added: 1521, mode: `MaxEncodedLen`) // Storage: `System::Account` (r:1 w:0) @@ -164,12 +207,12 @@ impl WeightInfo for () { // Proof: `DdcClusters::ClustersNodesStats` (`max_values`: None, `max_size`: None, mode: `Measured`) // Storage: `DdcClusters::ClustersNodes` (r:0 w:1) // Proof: `DdcClusters::ClustersNodes` (`max_values`: None, `max_size`: None, mode: `Measured`) - // Storage: UNKNOWN KEY `0x89eb0d6a8a691dae2cd15ed0369931ce0a949ecafa5c3f93f8121833646e15c3` (r:1 w:0) - // Proof: UNKNOWN KEY `0x89eb0d6a8a691dae2cd15ed0369931ce0a949ecafa5c3f93f8121833646e15c3` (r:1 w:0) - // Storage: UNKNOWN KEY `0xc3ad1d87683b6ac25f2e809346840d7a7ed0c05653ee606dba68aba3bdb5d957` (r:1 w:0) - // Proof: UNKNOWN KEY `0xc3ad1d87683b6ac25f2e809346840d7a7ed0c05653ee606dba68aba3bdb5d957` (r:1 w:0) - fn add_node() -> Weight { - Weight::from_parts(655_639_000_u64, 0) + // Storage: UNKNOWN KEY `0x11d2df4e979aa105cf552e9544ebd2b500000000` (r:1 w:0) + // Proof: UNKNOWN KEY `0x11d2df4e979aa105cf552e9544ebd2b500000000` (r:1 w:0) + // Storage: UNKNOWN KEY `0xee61cd03857d4d6515cbe7367c56239d5b1f4a8e800000000000000000000000` (r:1 w:0) + // Proof: UNKNOWN KEY `0xee61cd03857d4d6515cbe7367c56239d5b1f4a8e800000000000000000000000` (r:1 w:0) + fn join_cluster() -> Weight { + Weight::from_parts(656_290_000_u64, 0) .saturating_add(RocksDbWeight::get().reads(17_u64)) .saturating_add(RocksDbWeight::get().writes(6_u64)) } diff --git a/runtime/cere-dev/src/lib.rs b/runtime/cere-dev/src/lib.rs index db1e01a09..b2a7278ac 100644 --- a/runtime/cere-dev/src/lib.rs +++ b/runtime/cere-dev/src/lib.rs @@ -149,7 +149,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // and set impl_version to 0. If only runtime // implementation changes and behavior does not, then leave spec_version as // is and increment impl_version. - spec_version: 54127, + spec_version: 54128, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 19, diff --git a/runtime/cere/src/lib.rs b/runtime/cere/src/lib.rs index 7470590f1..72b65bc1b 100644 --- a/runtime/cere/src/lib.rs +++ b/runtime/cere/src/lib.rs @@ -143,7 +143,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // and set impl_version to 0. If only runtime // implementation changes and behavior does not, then leave spec_version as // is and increment impl_version. - spec_version: 54127, + spec_version: 541278, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 19,