Skip to content

Commit

Permalink
Merge pull request #104 from Cerebellum-Network/feature/ddc-staking-n…
Browse files Browse the repository at this point in the history
…ode-key

Associate DDC node IDs with staking account
  • Loading branch information
khssnv authored Oct 17, 2023
2 parents 69a2e2a + 2d6db47 commit d228f5b
Show file tree
Hide file tree
Showing 11 changed files with 158 additions and 48 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- [D] New `pallet-ddc-validator` which implements DDC CDN nodes validation and rewarding. You can enable DDC validation providing `--enable-ddc-validation` argument and `--dac-url` argument to specify DAC endpoint. It will only work on the nodes with validation and offchain workers enabled as well.
- [D] Several calls for `pallet-ddc-staking` to distribute rewards.
- [D] Third kind of account in DDC Staking for DDC nodes (along with stash and controller).
- [D] DDC cluster managers access control list in `pallet-ddc-staking` managed by governance.
- [Zombienet](https://github.com/paritytech/zombienet) configurations to test block building and spawn a network for DDC validation debugging.

Expand Down
27 changes: 20 additions & 7 deletions pallets/ddc-staking/src/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,22 @@ benchmarks! {
let controller = create_funded_user::<T>("controller", USER_SEED, 100);
let controller_lookup: <T::Lookup as StaticLookup>::Source
= T::Lookup::unlookup(controller.clone());
let node = create_funded_user::<T>("node", USER_SEED, 100);
let node_lookup: <T::Lookup as StaticLookup>::Source = T::Lookup::unlookup(node.clone());
let amount = T::Currency::minimum_balance() * 10u32.into();
whitelist_account!(stash);
}: _(RawOrigin::Signed(stash.clone()), controller_lookup, amount)
}: _(RawOrigin::Signed(stash.clone()), controller_lookup, node_lookup, amount)
verify {
assert!(Bonded::<T>::contains_key(stash));
assert!(Ledger::<T>::contains_key(controller));
assert!(Nodes::<T>::contains_key(node));
}

unbond {
// clean up any existing state.
clear_storages_and_edges::<T>();

let (stash, controller) = create_stash_controller::<T>(0, 100)?;
let (stash, controller, _) = create_stash_controller_node::<T>(0, 100)?;
let ledger = Ledger::<T>::get(&controller).ok_or("ledger not created before")?;
let original_bonded: BalanceOf<T> = ledger.active;
let amount = T::Currency::minimum_balance() * 5u32.into(); // Half of total
Expand All @@ -47,7 +50,7 @@ benchmarks! {
}

withdraw_unbonded {
let (stash, controller) = create_stash_controller::<T>(0, 100)?;
let (stash, controller, _) = create_stash_controller_node::<T>(0, 100)?;
let amount = T::Currency::minimum_balance() * 5u32.into(); // Half of total
DdcStaking::<T>::unbond(RawOrigin::Signed(controller.clone()).into(), amount)?;
CurrentEra::<T>::put(EraIndex::max_value());
Expand All @@ -62,7 +65,7 @@ benchmarks! {
}

store {
let (stash, controller) = create_stash_controller_with_balance::<T>(0, T::DefaultStorageBondSize::get())?;
let (stash, controller, _) = create_stash_controller_node_with_balance::<T>(0, T::DefaultStorageBondSize::get())?;

whitelist_account!(controller);
}: _(RawOrigin::Signed(controller), 1)
Expand All @@ -71,7 +74,7 @@ benchmarks! {
}

serve {
let (stash, controller) = create_stash_controller_with_balance::<T>(0, T::DefaultEdgeBondSize::get())?;
let (stash, controller, _) = create_stash_controller_node_with_balance::<T>(0, T::DefaultEdgeBondSize::get())?;

whitelist_account!(controller);
}: _(RawOrigin::Signed(controller), 1)
Expand All @@ -83,7 +86,7 @@ benchmarks! {
// clean up any existing state.
clear_storages_and_edges::<T>();

let (edge_stash, edge_controller) = create_stash_controller_with_balance::<T>(0, T::DefaultEdgeBondSize::get())?;
let (edge_stash, edge_controller, _) = create_stash_controller_node_with_balance::<T>(0, T::DefaultEdgeBondSize::get())?;
DdcStaking::<T>::serve(RawOrigin::Signed(edge_controller.clone()).into(), 1)?;
assert!(Edges::<T>::contains_key(&edge_stash));
CurrentEra::<T>::put(1);
Expand All @@ -97,7 +100,7 @@ benchmarks! {
}

set_controller {
let (stash, _) = create_stash_controller::<T>(USER_SEED, 100)?;
let (stash, _, _) = create_stash_controller_node::<T>(USER_SEED, 100)?;
let new_controller = create_funded_user::<T>("new_controller", USER_SEED, 100);
let new_controller_lookup = T::Lookup::unlookup(new_controller.clone());
whitelist_account!(stash);
Expand All @@ -106,6 +109,16 @@ benchmarks! {
assert!(Ledger::<T>::contains_key(&new_controller));
}

set_node {
let (stash, _, _) = create_stash_controller_node::<T>(USER_SEED, 100)?;
let new_node = create_funded_user::<T>("new_node", USER_SEED, 100);
let new_node_lookup = T::Lookup::unlookup(new_node.clone());
whitelist_account!(stash);
}: _(RawOrigin::Signed(stash), new_node_lookup)
verify {
assert!(Nodes::<T>::contains_key(&new_node));
}

allow_cluster_manager {
let new_cluster_manager = create_funded_user::<T>("cluster_manager", USER_SEED, 100);
let new_cluster_manager_lookup = T::Lookup::unlookup(new_cluster_manager.clone());
Expand Down
60 changes: 54 additions & 6 deletions pallets/ddc-staking/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,7 @@ pub mod pallet {
pub type Edges<T: Config> = StorageMap<_, Twox64Concat, T::AccountId, ClusterId>;

/// The map of (wannabe) storage network participants stash keys to the DDC cluster ID they wish
/// to participate into..
/// to participate into.
#[pallet::storage]
#[pallet::getter(fn storages)]
pub type Storages<T: Config> = StorageMap<_, Twox64Concat, T::AccountId, ClusterId>;
Expand Down Expand Up @@ -334,10 +334,15 @@ pub mod pallet {
#[pallet::getter(fn cluster_managers)]
pub type ClusterManagers<T: Config> = StorageValue<_, Vec<T::AccountId>, ValueQuery>;

/// Map from DDC node ID to the node operator stash account.
#[pallet::storage]
#[pallet::getter(fn nodes)]
pub type Nodes<T: Config> = StorageMap<_, Twox64Concat, T::AccountId, T::AccountId>;

#[pallet::genesis_config]
pub struct GenesisConfig<T: Config> {
pub edges: Vec<(T::AccountId, T::AccountId, BalanceOf<T>, ClusterId)>,
pub storages: Vec<(T::AccountId, T::AccountId, BalanceOf<T>, ClusterId)>,
pub edges: Vec<(T::AccountId, T::AccountId, T::AccountId, BalanceOf<T>, ClusterId)>,
pub storages: Vec<(T::AccountId, T::AccountId, T::AccountId, BalanceOf<T>, ClusterId)>,
pub settings: Vec<(ClusterId, BalanceOf<T>, EraIndex, BalanceOf<T>, EraIndex)>,
}

Expand Down Expand Up @@ -376,14 +381,15 @@ pub mod pallet {
}

// Add initial CDN participants
for &(ref stash, ref controller, balance, cluster) in &self.edges {
for &(ref stash, ref controller, ref node, balance, cluster) in &self.edges {
assert!(
T::Currency::free_balance(&stash) >= balance,
"Stash do not have enough balance to participate in CDN."
);
assert_ok!(Pallet::<T>::bond(
T::RuntimeOrigin::from(Some(stash.clone()).into()),
T::Lookup::unlookup(controller.clone()),
T::Lookup::unlookup(node.clone()),
balance,
));
assert_ok!(Pallet::<T>::serve(
Expand All @@ -393,14 +399,15 @@ pub mod pallet {
}

// Add initial storage network participants
for &(ref stash, ref controller, balance, cluster) in &self.storages {
for &(ref stash, ref controller, ref node, balance, cluster) in &self.storages {
assert!(
T::Currency::free_balance(&stash) >= balance,
"Stash do not have enough balance to participate in storage network."
);
assert_ok!(Pallet::<T>::bond(
T::RuntimeOrigin::from(Some(stash.clone()).into()),
T::Lookup::unlookup(controller.clone()),
T::Lookup::unlookup(node.clone()),
balance,
));
assert_ok!(Pallet::<T>::store(
Expand Down Expand Up @@ -442,7 +449,7 @@ pub mod pallet {
NotStash,
/// Stash is already bonded.
AlreadyBonded,
/// Controller is already paired.
/// Controller or node is already paired.
AlreadyPaired,
/// Cannot have a storage network or CDN participant, with the size less than defined by
/// governance (see `BondSize`). If unbonding is the intention, `chill` first to remove
Expand Down Expand Up @@ -500,6 +507,7 @@ pub mod pallet {
pub fn bond(
origin: OriginFor<T>,
controller: <T::Lookup as StaticLookup>::Source,
node: <T::Lookup as StaticLookup>::Source,
#[pallet::compact] value: BalanceOf<T>,
) -> DispatchResult {
let stash = ensure_signed(origin)?;
Expand All @@ -519,8 +527,17 @@ pub mod pallet {
Err(Error::<T>::InsufficientBond)?
}

let node = T::Lookup::lookup(node)?;

// Reject a bond with a known DDC node.
if Nodes::<T>::contains_key(&node) {
Err(Error::<T>::AlreadyPaired)?
}

frame_system::Pallet::<T>::inc_consumers(&stash).map_err(|_| Error::<T>::BadState)?;

Nodes::<T>::insert(&node, &stash);

// You're auto-bonded forever, here. We might improve this by only bonding when
// you actually store/serve and remove once you unbond __everything__.
<Bonded<T>>::insert(&stash, &controller);
Expand Down Expand Up @@ -906,6 +923,33 @@ pub mod pallet {

Ok(())
}

/// (Re-)set the DDC node of a node operator stash account. Requires to chill first.
///
/// The dispatch origin for this call must be _Signed_ by the stash, not the controller.
#[pallet::weight(T::WeightInfo::set_node())]
pub fn set_node(
origin: OriginFor<T>,
new_node: <T::Lookup as StaticLookup>::Source,
) -> DispatchResult {
let stash = ensure_signed(origin)?;

let new_node = T::Lookup::lookup(new_node)?;

if let Some(existing_node_stash) = Nodes::<T>::get(&new_node) {
if existing_node_stash != stash {
Err(Error::<T>::AlreadyPaired)?
}
}

// Ensure only one node per stash during the DDC era.
ensure!(!<Edges<T>>::contains_key(&stash), Error::<T>::AlreadyInRole);
ensure!(!<Storages<T>>::contains_key(&stash), Error::<T>::AlreadyInRole);

<Nodes<T>>::insert(new_node, stash);

Ok(())
}
}

impl<T: Config> Pallet<T> {
Expand Down Expand Up @@ -1021,6 +1065,10 @@ pub mod pallet {
<Bonded<T>>::remove(stash);
<Ledger<T>>::remove(&controller);

if let Some((node, _)) = <Nodes<T>>::iter().find(|(_, v)| v == stash) {
<Nodes<T>>::remove(node);
}

Self::do_remove_storage(stash);
Self::do_remove_edge(stash);

Expand Down
12 changes: 6 additions & 6 deletions pallets/ddc-staking/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -195,17 +195,17 @@ impl ExtBuilder {
let mut edges = vec![];
if self.has_edges {
edges = vec![
// (stash, controller, stake, cluster)
(11, 10, 100, 1),
(21, 20, 100, 1),
// (stash, controller, node, stake, cluster)
(11, 10, 12, 100, 1),
(21, 20, 22, 100, 1),
];
}
let mut storages = vec![];
if self.has_storages {
storages = vec![
// (stash, controller, stake, cluster)
(31, 30, 100, 1),
(41, 40, 100, 1),
// (stash, controller, node, stake, cluster)
(31, 30, 32, 100, 1),
(41, 40, 42, 100, 1),
];
}

Expand Down
30 changes: 22 additions & 8 deletions pallets/ddc-staking/src/testing_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,29 +43,43 @@ pub fn create_funded_user_with_balance<T: Config>(
}

/// Create a stash and controller pair.
pub fn create_stash_controller<T: Config>(
pub fn create_stash_controller_node<T: Config>(
n: u32,
balance_factor: u32,
) -> Result<(T::AccountId, T::AccountId), &'static str> {
) -> Result<(T::AccountId, T::AccountId, T::AccountId), &'static str> {
let stash = create_funded_user::<T>("stash", n, balance_factor);
let controller = create_funded_user::<T>("controller", n, balance_factor);
let controller_lookup: <T::Lookup as StaticLookup>::Source =
T::Lookup::unlookup(controller.clone());
let node = create_funded_user::<T>("node", n, balance_factor);
let node_lookup: <T::Lookup as StaticLookup>::Source = T::Lookup::unlookup(node.clone());
let amount = T::Currency::minimum_balance() * (balance_factor / 10).max(1).into();
DdcStaking::<T>::bond(RawOrigin::Signed(stash.clone()).into(), controller_lookup, amount)?;
return Ok((stash, controller))
DdcStaking::<T>::bond(
RawOrigin::Signed(stash.clone()).into(),
controller_lookup,
node_lookup,
amount,
)?;
return Ok((stash, controller, node))
}

/// Create a stash and controller pair with fixed balance.
pub fn create_stash_controller_with_balance<T: Config>(
pub fn create_stash_controller_node_with_balance<T: Config>(
n: u32,
balance: crate::BalanceOf<T>,
) -> Result<(T::AccountId, T::AccountId), &'static str> {
) -> Result<(T::AccountId, T::AccountId, T::AccountId), &'static str> {
let stash = create_funded_user_with_balance::<T>("stash", n, balance);
let controller = create_funded_user_with_balance::<T>("controller", n, balance);
let controller_lookup: <T::Lookup as StaticLookup>::Source =
T::Lookup::unlookup(controller.clone());
let node = create_funded_user_with_balance::<T>("node", n, balance);
let node_lookup: <T::Lookup as StaticLookup>::Source = T::Lookup::unlookup(node.clone());

DdcStaking::<T>::bond(RawOrigin::Signed(stash.clone()).into(), controller_lookup, balance)?;
Ok((stash, controller))
DdcStaking::<T>::bond(
RawOrigin::Signed(stash.clone()).into(),
controller_lookup,
node_lookup,
balance,
)?;
Ok((stash, controller, node))
}
9 changes: 5 additions & 4 deletions pallets/ddc-staking/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,12 +109,12 @@ fn staking_should_work() {
let _ = Balances::make_free_balance_be(&i, 2000);
}

// Add new CDN participant, account 3 controlled by 4.
assert_ok!(DdcStaking::bond(RuntimeOrigin::signed(3), 4, 1500));
// Add new CDN participant, account 3 controlled by 4 with node 5.
assert_ok!(DdcStaking::bond(RuntimeOrigin::signed(3), 4, 5, 1500));
assert_ok!(DdcStaking::serve(RuntimeOrigin::signed(4), 1));

// Account 4 controls the stash from account 3, which is 1500 units and 3 is a CDN
// participant.
// Account 4 controls the stash from account 3, which is 1500 units, 3 is a CDN
// participant, 5 is a DDC node.
assert_eq!(DdcStaking::bonded(&3), Some(4));
assert_eq!(
DdcStaking::ledger(&4),
Expand All @@ -127,6 +127,7 @@ fn staking_should_work() {
})
);
assert_eq!(DdcStaking::edges(3), Some(1));
assert_eq!(DdcStaking::nodes(5), Some(3));

// Set `CurrentEra`.
Timestamp::set_timestamp(System::block_number() * BLOCK_TIME + INIT_TIMESTAMP);
Expand Down
Loading

0 comments on commit d228f5b

Please sign in to comment.