Skip to content

Commit

Permalink
New join_cluster extrinsic (#425)
Browse files Browse the repository at this point in the history
## 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.
<!--- Check the following box with an x if the following applies: -->
- [ ] Hotfix
- [ ] Release
- [x] Fix or Feature

## Fix or Feature
<!--- Check the following box with an x if the following applies: -->

### Types of Changes
<!--- What types of changes does your code introduce? -->
- [ ] 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
<!--- All boxes need to be checked. Follow this checklist before
requiring PR review -->
- [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
  • Loading branch information
khssnv authored Oct 8, 2024
1 parent 18369b0 commit d9cc7c7
Show file tree
Hide file tree
Showing 11 changed files with 847 additions and 357 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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]

Expand Down
13 changes: 13 additions & 0 deletions pallets/ddc-clusters/src/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,19 @@ benchmarks! {
assert!(ClustersNodes::<T>::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::<T::AccountId>("user", USER_SEED, 0u32);
let balance = <T as pallet::Config>::Currency::minimum_balance() * 1_000_000u32.into();
let _ = <T as pallet::Config>::Currency::make_free_balance_be(&user, balance);
let _ = config_cluster_and_node::<T>(user.clone(), node_pub_key.clone(), cluster_id);
}: _(RawOrigin::Signed(user.clone()), cluster_id, node_pub_key.clone())
verify {
assert!(ClustersNodes::<T>::contains_key(cluster_id, node_pub_key));
}

remove_node {
let bytes = [0u8; 32];
let node_pub_key = NodePubKey::StoragePubKey(AccountId32::from(bytes));
Expand Down
104 changes: 89 additions & 15 deletions pallets/ddc-clusters/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ pub mod pallet {
AttemptToRemoveNonExistentNode,
AttemptToRemoveNotAssignedNode,
OnlyClusterManager,
OnlyNodeProvider,
NodeIsNotAuthorized,
NodeHasNoActivatedStake,
NodeStakeIsInvalid,
Expand Down Expand Up @@ -311,23 +312,9 @@ pub mod pallet {
ensure!(!has_chilling_attempt, Error::<T>::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::<T>::AttemptToAddNonExistentNode)?;

// Cluster extension smart contract allows joining.
if let Some(address) = cluster.props.node_provider_auth_contract.clone() {
let auth_contract = NodeProviderAuthContract::<T>::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::<Error<T>>::into)?;
ensure!(is_authorized, Error::<T>::NodeIsNotAuthorized);
};

Self::do_add_node(cluster, node_pub_key, node_kind)
}

Expand Down Expand Up @@ -394,6 +381,55 @@ pub mod pallet {

Self::do_validate_node(cluster_id, node_pub_key, succeeded)
}

#[pallet::call_index(5)]
#[pallet::weight(<T as pallet::Config>::WeightInfo::join_cluster())]
pub fn join_cluster(
origin: OriginFor<T>,
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::<T>::try_get(cluster_id).map_err(|_| Error::<T>::ClusterDoesNotExist)?;
let node_provider_auth_contract_address = cluster
.props
.node_provider_auth_contract
.clone()
.ok_or(Error::<T>::NodeIsNotAuthorized)?;

// Node with this public key exists and belongs to the caller.
let node = T::NodeRepository::get(node_pub_key.clone())
.map_err(|_| Error::<T>::AttemptToAddNonExistentNode)?;
ensure!(*node.get_provider_id() == caller_id, Error::<T>::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::<Error<T>>::into)?;
ensure!(has_activated_stake, Error::<T>::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::<Error<T>>::into)?;
ensure!(!has_chilling_attempt, Error::<T>::NodeChillingIsProhibited);

// Cluster auth smart contract allows joining.
let auth_contract =
NodeProviderAuthContract::<T>::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::<Error<T>>::into)?;
ensure!(is_authorized, Error::<T>::NodeIsNotAuthorized);

Self::do_join_cluster(cluster, node_pub_key)
}
}

impl<T: Config> Pallet<T> {
Expand Down Expand Up @@ -538,6 +574,44 @@ pub mod pallet {
Ok(())
}

fn do_join_cluster(
cluster: Cluster<T::AccountId>,
node_pub_key: NodePubKey,
) -> DispatchResult {
ensure!(cluster.can_manage_nodes(), Error::<T>::UnexpectedClusterStatus);

let mut node: pallet_ddc_nodes::Node<T> = T::NodeRepository::get(node_pub_key.clone())
.map_err(|_| Error::<T>::AttemptToAddNonExistentNode)?;
ensure!(node.get_cluster_id().is_none(), Error::<T>::AttemptToAddAlreadyAssignedNode);

node.set_cluster_id(Some(cluster.cluster_id));
T::NodeRepository::update(node).map_err(|_| Error::<T>::AttemptToAddNonExistentNode)?;

ClustersNodes::<T>::insert(
cluster.cluster_id,
node_pub_key.clone(),
ClusterNodeState {
kind: ClusterNodeKind::External,
status: ClusterNodeStatus::ValidationSucceeded,
added_at: frame_system::Pallet::<T>::block_number(),
},
);
Self::deposit_event(Event::<T>::ClusterNodeAdded {
cluster_id: cluster.cluster_id,
node_pub_key,
});

let mut current_stats = ClustersNodesStats::<T>::try_get(cluster.cluster_id)
.map_err(|_| Error::<T>::ClusterDoesNotExist)?;
current_stats.validation_succeeded = current_stats
.validation_succeeded
.checked_add(1)
.ok_or(Error::<T>::ArithmeticOverflow)?;
ClustersNodesStats::<T>::insert(cluster.cluster_id, current_stats);

Ok(())
}

fn do_remove_node(
cluster: Cluster<T::AccountId>,
node_pub_key: NodePubKey,
Expand Down
2 changes: 1 addition & 1 deletion pallets/ddc-clusters/src/node_provider_auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ where
.result
.map_err(|_| NodeProviderAuthContractError::ContractCallFailed)?
.data
.first()
.get(1)
.is_some_and(|x| *x == 1);

Ok(is_authorized)
Expand Down
Loading

0 comments on commit d9cc7c7

Please sign in to comment.