---
title: Preventing max unbonding pools slots filled in nomination pools
tags: parity, FRAME, substrate, nomination-pools, staking
---
## Preventing max unbonding pools slots filled in nomination pools
When a nomination pool member unbonds funds from a nomination pool, the points and balances in the bonding pool are burned and minted in a per-era unbonding pool which keeps track of all the points and balance that have been unbond in a specific era. The funds can be explicitly withdrawn after the unbonding period passed.
The staking pallet sets a limit on how many pending unbonded per era slots are scheduled (`T::MaxUnlockingChunks`). If this limit is hit, the staking pallet will fail to unbond more funds associated with different eras and return an error `Error::<T>::NoMoreChunks`. The subsequent unbonding attempts will fail until at least one slot is available. This behaviour creates an attack vector in which an attacker may unbond across multiple eras without withdrawing to fill the unlocking chunks and prevent other pool members to unbond. The same behaviour could happen without malicious intent, when pool members forget to withdraw after the unbonding duration.
Since withdrawal is premissionless, we can run a bot that periodically calls withdraw funds that can be released. There are advantages and disadvantages in this approach. The advantage is that the blockchain does not need to perform extra actions that could/should be enacted by clients, in this case the funds withdraw. ~~The disadvantage is that a DoS attack vector is dependent on the existence of an external off-chain entity. This is a reasonable solution for the short term but may pose risks in the long term, as the number of "safety" bots (and associated ops/ maintenance burden) increase across the ecosystem.~~
One solution to not having to rely on bots is for the nomination pool pallet to request withdrawal of the oldest unbonding slot if the slots are all fulfilled. This action will not impact significantly the costs of the nomination pools (double check statement). There a few solutions to implement the implicit fund withdrawal when there are no available chunk slots.
**Solution 1**: At every unbonding request, the node tries to withdraw all or at least one chunk before unbonding. This way, we ensure that if a new chunk is created after unbonding, there will be space in the queue.
This can be implemented in one of two layers:
1. At the `StakingInterface` user, where an `withdraw()` and re try `unbond()` are called if a `T::MaxUnlockingChunks` error is returned by the `StakingInterface`:
```rust
// (1)
fn unbond<T>() {
// ..
match T::StakingInterface::unbond().unwrap_or_else(|err| {
if err == Error::<T>::NoMoreChunks {
Self::withdraw_and_retry_unbond()
} else {
// ..
}
})
}
fn withdraw_and_retry_unbond<T>() {
T::StakingInterface::withdraw();
Self::unbond() // retry
}
```
2. The staking interface exposes a new method `apply_requirements_and_unbond()`, which applies any sensible requirement before trying to unbond, in order to make sure the unbond happens. A "requirement" to be applied may vary by implementer. In the case of the staking pallet, the requirement is that there is at least one free chunk slot available before applying the unbond. The `unbond()` method can still be used.
```rust
// (2)
trait StakingInterface {
fn fn unbond(controller: Self::AccountId, value: Self::Balance) -> DispatchResult
fn apply_requirements_and_unbond(controller: Self::AccountId, value: Self::Balance) -> DispatchResult
}
```
In this case, the nomination pools pallet will replace all the calls for `T::StakingInterface::unbond()` to `T::StakingInterface::apply_requirements_and_unbond()`
**Solution 2:** The remove the need for explicit withdrawal altogether and make sure all the locked chunks that are released after the unbonding period are explicitly withdrawn. This could be implemented in the staking pallet by withdrawing all "releasable" funds at `Hooks::on_initialize`.
This would simplify considerably the whole flow and make the staking in general more user friendly. However, By principle, the pallets should offload the actions that can be done by the client/interface.
---
I personally like the option 1.2, since it is not a breaking change for the `StakingInterface` users and it gives the pallet developer flexibility while keeping the code relatively clean and without retries. It also achieves a middle ground between the positions "All operations that can be performed by users, should not be done by the pallet" and "The pallet will explicitly call all the operations for the user".