Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: interface and data structures for Collator Power Pallet #53

Closed
wants to merge 12 commits into from
15 changes: 15 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ license-file = "LICENSE"
repository = "https://github.com/eigerco/polka-storage"

[workspace]
members = ["node", "runtime"]
members = ["node", "pallets/collator-power", "runtime"]
resolver = "2"

# FIXME(#@jmg-duarte,#7,14/5/24): remove the patch once something >1.11.0 is released
Expand Down Expand Up @@ -51,6 +51,7 @@ thiserror = { version = "1.0.48" }
tracing-subscriber = { version = "0.3.18" }

# Local
pallet-collator-power = { path = "pallets/collator-power", default-features = false }
polka-storage-runtime = { path = "runtime" }

# Substrate
Expand Down
Empty file removed pallets/collator-power/.gitkeep
Empty file.
50 changes: 50 additions & 0 deletions pallets/collator-power/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
[package]
authors.workspace = true
description = "manages collators' power used in the selection process of a collator node"
edition.workspace = true
homepage.workspace = true
license-file.workspace = true
name = "pallet-collator-power"
publish = false
repository.workspace = true
version = "0.0.0"

[lints]
workspace = true

[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]

[dependencies]
codec = { workspace = true, default-features = false, features = ["derive"] }
scale-info = { workspace = true, default-features = false, features = ["derive"] }

# frame deps
frame-benchmarking = { workspace = true, default-features = false, optional = true }
frame-support = { workspace = true, default-features = false }
frame-system = { workspace = true, default-features = false }

[dev-dependencies]
sp-core = { workspace = true, default-features = false }
sp-io = { workspace = true }
sp-runtime = { workspace = true, default-features = false }

[features]
default = ["std"]
runtime-benchmarks = [
"frame-benchmarking/runtime-benchmarks",
"frame-support/runtime-benchmarks",
"frame-system/runtime-benchmarks",
"sp-runtime/runtime-benchmarks",
]
std = [
"codec/std",
"frame-benchmarking?/std",
"frame-support/std",
"frame-system/std",
"scale-info/std",
"sp-core/std",
"sp-io/std",
"sp-runtime/std",
]
try-runtime = ["frame-support/try-runtime", "frame-system/try-runtime", "sp-runtime/try-runtime"]
154 changes: 154 additions & 0 deletions pallets/collator-power/DESIGN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
# Overall Power Pallet Flow

## Glossary
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we are using mono repo for our work, the glossary could be made in a separate, higher-level document to keep terminology consistent across the project.

For an overview of established terms across the project look [here](../../docs/glossary.md).
This is just a handy index for shortcuts that are used in **this** design doc.

- `SPP` - Storage Provider Pallet
- `CPP` - Collator Power Pallet
- `CSP` - Collator Selection Pallet
- `CRP` - Collator Reward Pallet

## Overview

**Collators** are entities selected to produce state transition proofs which are then finalized by relay chain's **validators**.
They aggregate parachain transactions into **parachain block candidates*.
To participate in **block candidate production**, a **Collator** needs to stake some **tokens**.
Proportionally to the amount of **tokens**, a **Collator** has a higher chance to be selected for the **block candidate production**.
**Collator** can stake his own tokens or a **Storage Provider** can delegate his tokens to the **Collator**.
**Storage Provider** by doing that can earn some tokens, when the **Collator** he delegated his tokens on is chosen for the production.
When a **Collator** is slashed, **Storage Provider** that staked their tokens on them is slashed accordingly.

**Storage Providers** do not need to stake any tokens on Collator to support their storage resources, it's optional.
When **Storage Providers** misbehave e.g. fail to deliver some proof, they're being slashed from the collateral they pledged when for example:
- securing a new deal with a customer,
- adding storage capacity (which requires pledging).

This pallet works as a proxy between `SPP` and `CSP` to make collator choices.
It stores how much power was delegated by **Miners** to **Collators**.
Both `SPP` and `CSP` are [tightly coupled][2] to this pallet.

## Data Structures

```rust
/// Store of Collators and their metadata
collators: BoundedBTreeMap<CollatorId, StoragePower, ConstU32<100>>
/// List of available Storaged Providers
/// Used as an allowlist for who can stake on a Collator
storage_providers: BoundedBTreeSet<StorageProviderId>

struct CollatorInfo<Collator, StorageProvider, Power> {
/// Identifier of a Collator
who: Collator,
/// Reserved deposit of a Collator
deposit: Power,
/// Delegated deposits from Storage Providers to Collators
delegated_deposit: Map<StorageProvider, Power>
}
```

## Use Cases

### Storage Provider Registration

We need to identify storage providers somehow.
Calling a `Storage Provider Pallet` would create a circular dependency.
The `SPP` will call the registration function to let the `CPP` now, that a **Storage Provider**
is allowed to stake Power (tokens) on a **Collator**.

#### Assumptions
- `register_storage_provider(storage_provider: T::StorageProviderId)` is a **plain function**, it's called by `Storage Provider Pallet` when a new Storage Provider is registered, we trust the caller. It can only be called from `SPP` via [tight coupling][2].

#### Flow:
1. `SPP` calls `register_storage_provider(storage_provider: T::StorageProviderId)`
2. `CPP` adds a `storage provider` to the `TreeSet` keeping the list of registered providers

### Collator Registration

#### Assumptions

- **Collator** can register on its own by calling an extrinsic `register_as_collator()`.
- It requires a certain minimum amount of **collateral** (a bond) to be locked, to become a **collator**.
- After you registered as a **collator**, you can update your bond and lock even more **collateral**.

#### Flow

1. A node in the network calls `CPP.register_as_collator(origin: T::CollatorId)`
2. `CPP` verifies whether a account that originated the transaction has a minimum amount of **collateral** to be deposited.
3. `CPP` reserves (locks) deposited balance of the account, through `ReservableCurrency`
3. `CPP` adds `CollatorId` to the `Map<Collator, CollatorInfo>` with the `deposit` equal to the minimum **bond**.

### Adding more Collator Power as a Collator

#### Assumptions

- `CPP.update_bond` is an **extrinsic**, which is called by a **Collator**.
- You cannot update bond on a *Collator* that has not been registered before with `CPP.register_as_collator`
- `CPP.update_bond` can reduce as well as increase deposit, hence the Power

#### Flow

1. **Collator** calls `CPP.update_bond(collator: T::CollatorId, new_deposit: BalanceOf<T>)`
2. In the next **session**, the saved Power is picked up by `CSP`, by calling `CPP.get_collator_power(collator: T::CollatorId) -> T::StoragePower`.

### Delegating power to a Collator as a Storage Provider

#### Assumptions

- `update_storage_provider_bond()` is an **extrinsic** that can be called by **Storage Providers**
- **Storage Provider** is present in the `storage_providers` set - has been registered with `CPP.register_storage_provider`.
- **Collator** has been registerd in `collators` TreeMap

#### Flow

1. **Storage Provider** calls `CPP.update_storage_provider_bond(storage_provider: T::StorageProviderId, collator: T:CollatorId, new_deposit: BalanceOf<T>)`
2. In the next **session**, the saved Power is picked up by `CSP`, by calling `CPP.get_collator_power(collator: T::CollatorId) -> T::StoragePower`.


### Getting list of Collator Candidates

`Collator Selection Pallet` has it's own list of **invulnerables**, to get select next **Collator** it'll also used candidates based on the power defined in this pallet.

#### Assumptions

- `CPP.get_collator_candidates() -> BoundedVec<CollatorId, Power>` is a **plain function** that is called by `CSP` at the end of a session.

#### Flow

1. `CSP` calls `CPP.get_collator_candidates()`
2. `CPP` returns candidate list sorted by `Power`

### Storage Provider Slashing

When Storage Provider misbehaves, `Storage Provider Pallet` slashes the **Storage Provider** internally calls `update_storage_provider_bond` to decrease delegated **Power**.

We need to consider:
- Eras vs Sessions

### Collator Slashing

****VERY UNSTABLE START*****
When Collator misbehaves, i.e. produces invalid block, someone needs to slash him, but who?
How does it work? Why is it important? Because then we also need to slash Storage Providers that backed him.
Lots of useful info is in the pallet reponsible for [`frame/staking`][7], we can probably use lots of implementation from there.
Seems complex enough though. It basically implements Substrate [NPoS][10], which we want to use, but with a twist.
Our twist is that, only **Storage Provider** can become **a Nominator**. Whether that's good, we are yet to determine.

Overall process looks like this:
- [pallet_babe][8] - [BABE][9] consensus has a constant set of validators (collators) in an epoch, epoch is divided in slots. For every slot a validator is selected randomly. If other validators detect, that the leader fails, the process of **equivocation** is launched.
- [pallet_offences][11] - pallet offences exposes an `OffenceHandler` interface, which is used by `pallet_babe`.
- [pallet_staking][7] - handles **Validator**'s and **Nominators** balances, and implements `OffenceHandler` defined by `pallet_offences` and used by `pallet_babe`.

****VERY UNSTABLE END****

[1]: https://github.com/filecoin-project/lotus/blob/9851d35a3811e5339560fb706926bf63a846edae/cmd/lotus-miner/init.go#L638
[2]: https://paritytech.github.io/polkadot-sdk/master/polkadot_sdk_docs/reference_docs/frame_pallet_coupling/index.html#tight-coupling-pallets
[3]: https://spec.filecoin.io/#section-algorithms.pos
[4]: https://paritytech.github.io/polkadot-sdk/master/pallet_session/index.html
[5]: https://github.com/eigerco/polka-disk/blob/main/doc/research/lotus/lotus-overview.md#Roles
[6]: https://spec.filecoin.io/#section-algorithms.pos.post
[7]: https://github.com/paritytech/polkadot-sdk/blob/master/substrate/frame/staking/README.md
[8]: https://paritytech.github.io/polkadot-sdk/master/pallet_babe/index.html
[9]: https://research.web3.foundation/Polkadot/protocols/block-production/Babe
[10]: https://research.web3.foundation/Polkadot/protocols/NPoS/Overview
[11]: https://paritytech.github.io/polkadot-sdk/master/pallet_offences/index.html
2 changes: 2 additions & 0 deletions pallets/collator-power/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Collator Power Pallet

88 changes: 88 additions & 0 deletions pallets/collator-power/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
//! # Collator Power Pallet
//!
//! # Overview
//!
//! The Collator Power Pallet provides functions for:
//! - ...

#![cfg_attr(not(feature = "std"), no_std)]

pub use pallet::*;

#[frame_support::pallet(dev_mode)]
pub mod pallet {
use codec::{Decode, Encode};
use frame_support::{
dispatch::DispatchResultWithPostInfo,
pallet_prelude::*,
sp_runtime::RuntimeDebug,
};
use frame_system::{pallet_prelude::*};
use scale_info::TypeInfo;

#[pallet::config]
pub trait Config: frame_system::Config {
/// Because this pallet emits events, it depends on the runtime's definition of an event.
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;

/// Unit of Storage Power of a Miner
/// E.g. `u128`, used as `number of bytes` for a given SP.
type StoragePower: Parameter + Member + Clone + MaxEncodedLen;

/// A stable ID for a Collator
type CollatorId: Parameter + Member + Ord + MaxEncodedLen;

/// A stable ID for a Miner
type MinerId: Parameter + Member + Ord + MaxEncodedLen;
}

#[derive(
Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, Default, TypeInfo, MaxEncodedLen,
)]
pub struct MinerClaim<CollatorId: Ord, StoragePower> {
/// Number of bytes stored by a Miner
raw_bytes_power: StoragePower,
th7nder marked this conversation as resolved.
Show resolved Hide resolved
/// Stores how much currency was staked on a particular collator
staked_power: BoundedBTreeMap<CollatorId, StoragePower, ConstU32<10>>
}

#[pallet::pallet]
pub struct Pallet<T>(_);
th7nder marked this conversation as resolved.
Show resolved Hide resolved

#[pallet::storage]
#[pallet::getter(fn storage_provider_claims)]
pub type MinerClaims<T: Config> =
StorageMap<_, _, T::MinerId, MinerClaim<T::CollatorId, T::StoragePower>>;

#[pallet::event]
// #[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
/// Indicates that a new Miner has been registered.
/// Newly created Miner does not have any Power.
/// Power is updated after the Miner proves it has storage available.
MinerRegistered(T::AccountId),
}

#[pallet::error]
pub enum Error<T> {
/// If there is an entry in claims map, connected to the AccountId that tries to be registered as a Miner.
MinerAlreadyRegistered,
}

/// Extrinsics exposed by the pallet
#[pallet::call]
impl<T: Config> Pallet<T> {
/// After Miner proved a sector, calls this method to update the bookkeeping about available power.
pub fn update_storage_power(
_storage_provider: OriginFor<T>,
_raw_delta_bytes: T::StoragePower,
) -> DispatchResultWithPostInfo {
todo!()
}
}

/// Functions exposed by the pallet
/// e.g. `pallet-collator-selection` used them to make decision about the next block producer
impl<T: Config> Pallet<T> {
}
}
6 changes: 6 additions & 0 deletions runtime/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ pallet-timestamp = { workspace = true, default-features = false }
pallet-transaction-payment = { workspace = true, default-features = false }
pallet-transaction-payment-rpc-runtime-api = { workspace = true, default-features = false }

# Local Pallets
pallet-collator-power = { workspace = true, default-features = false }

# Substrate Primitives
sp-api = { workspace = true, default-features = false }
sp-block-builder = { workspace = true, default-features = false }
Expand Down Expand Up @@ -103,6 +106,7 @@ std = [
"pallet-aura/std",
"pallet-authorship/std",
"pallet-balances/std",
"pallet-collator-power/std",
"pallet-collator-selection/std",
"pallet-message-queue/std",
"pallet-session/std",
Expand Down Expand Up @@ -146,6 +150,7 @@ runtime-benchmarks = [
"frame-system/runtime-benchmarks",
"hex-literal",
"pallet-balances/runtime-benchmarks",
"pallet-collator-power/runtime-benchmarks",
"pallet-collator-selection/runtime-benchmarks",
"pallet-message-queue/runtime-benchmarks",
"pallet-sudo/runtime-benchmarks",
Expand All @@ -171,6 +176,7 @@ try-runtime = [
"pallet-aura/try-runtime",
"pallet-authorship/try-runtime",
"pallet-balances/try-runtime",
"pallet-collator-power/try-runtime",
"pallet-collator-selection/try-runtime",
"pallet-message-queue/try-runtime",
"pallet-session/try-runtime",
Expand Down
7 changes: 7 additions & 0 deletions runtime/src/configs/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -303,3 +303,10 @@ impl pallet_collator_selection::Config for Runtime {
type ValidatorRegistration = Session;
type WeightInfo = ();
}

impl pallet_collator_power::Config for Runtime {
type RuntimeEvent = RuntimeEvent;
type StoragePower = u128;
type CollatorId = <Self as frame_system::Config>::AccountId;
type MinerId = <Self as frame_system::Config>::AccountId;
}
3 changes: 3 additions & 0 deletions runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,9 @@ construct_runtime!(
PolkadotXcm: pallet_xcm = 31,
CumulusXcm: cumulus_pallet_xcm = 32,
MessageQueue: pallet_message_queue = 33,

// Polka Storage Pallets
CollatorPower: pallet_collator_power = 50,
th7nder marked this conversation as resolved.
Show resolved Hide resolved
}
);

Expand Down