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"]
93 changes: 93 additions & 0 deletions pallets/collator-power/DESIGN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# 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.

- **extrinsic** - state transition function on a pallet, essentially a signed transaction which requires an account with some tokens to be executed as it costs fees.
- **Miner** - [Storage Provider][5]
Copy link
Contributor

@serg-temchenko serg-temchenko May 31, 2024

Choose a reason for hiding this comment

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

In our solution, where storage providing and block production is done by separate roles/entities using miner is misleading, since mining is a process of block production. Lotus might use this term since storage provide and block producer in their ecosystem is the same role/entity.

- **collateral** - amount of tokens staked by a miner (via `SPP`) to be able to provide storage services
- **PoSt** - [Proof of Storage][3]
th7nder marked this conversation as resolved.
Show resolved Hide resolved
- `SPP` - Storage Provider Pallet
- `CPP` - Collator Power Pallet
- `CSP` - Collator Selection Pallet
- **session** - a [session][4] is a period of time that has a constant set of validators.

## Overview

**Collators** are entities selected to produce **blocks** which are then finalized by relay chain's **validators**.
th7nder marked this conversation as resolved.
Show resolved Hide resolved
To participate in **block production**, a **Collator** needs to:
- stake a ***certain*** (yet to be determined) amount of tokens
th7nder marked this conversation as resolved.
Show resolved Hide resolved
- be backed by **Miners'** **Storage Power**.
Collators' staking is a requirement for participation in the process, the actual selection is based on **Storage Power**.
th7nder marked this conversation as resolved.
Show resolved Hide resolved

The collators are selected based on **Storage Power** by `CSP` in an randonmized auction algorithm.
The more **Storage Power** has been staked on a **Collator** by the **Miner**, the more likely their chances to be selected for block production.
th7nder marked this conversation as resolved.
Show resolved Hide resolved

This pallet works as a proxy between `SPP` and `CSP` to make collator choices.
It stores how much **Storage Power** a **Miner** has and how much was delegated by **Miners** to **Collators**.
th7nder marked this conversation as resolved.
Show resolved Hide resolved
Both `SPP` and `CSP` are [tightly coupled][2] to this pallet.

Trade Offs [?]:

**Collators** are separately tracked by `CSP` and this pallet gives back the `staked_power` to a **Miner** when a **Collator** disappears.
This is an intentional design decision, this could be also tracked in this pallet, however I do think it'd make this Pallet too complex.
As Collators also need to be staked and require their own registration logic.

## Data Structures

```rust
struct MinerClaim {
th7nder marked this conversation as resolved.
Show resolved Hide resolved
/// Indicates how much power a Miner has
raw_bytes_power: T::StoragePower;
th7nder marked this conversation as resolved.
Show resolved Hide resolved
staked_power: Map<T::CollatorId, T::StoragePower>
th7nder marked this conversation as resolved.
Show resolved Hide resolved
}
```

## Use Cases

### Registration

#### Useful links
- [Creating Storage Miner in Lotus][1]
th7nder marked this conversation as resolved.
Show resolved Hide resolved

#### Assumptions
- `create_miner(miner: T::MinerId)` is an **extrinsic**. It's called by `Storage Provider Node` on a bootstrap. We can trust it, as `T::MinerId` is essentialy an account ID. The transaction needs to be signed, for it to be signed it needs to come from an account. For it to come from an account, the account has to have an **existential deposit** and it costs money. That's how it's DOS-resistant.
th7nder marked this conversation as resolved.
Show resolved Hide resolved
th7nder marked this conversation as resolved.
Show resolved Hide resolved

#### Flow:
1. **Miner** calls `create_miner(miner: T::MinerId)`
2. `CPP` initializes `Map<MinerId, MinerClaim>`.

### Manipulating Power

#### Assumptions
- `SPP` only calls `CPP.update_miner_power` function after:
* Miner has been registered in `Collator Power` via `create_miner` function call
* Miner has submitted `PreCommit` sector with a certain (calculated by `SPP`) amount **Collateral** required
* Miner has proven sectors with **PoSt** via `ProveCommit` of `SPP`.
- `update_miner_power` is ***NOT** an **extrinsic**. It can only be called from `SPP` via [tight coupling][2].
- The reason is that we can't trust that a **Miner** will call extrinsic and update it on their own. `SPP` logic will perform those updates, e.g: after (not)receiving **PoSt**, receiving **pledge collaterals**.
th7nder marked this conversation as resolved.
Show resolved Hide resolved

#### Flow
1. `SPP` calls `CPP.update_miner_power(miner: T::MinerId, deltaStorageBytes: T::StoragePower)`
2. If Storage Power was decresed: `CPP` decreases all delegated power (to **Collators**)
- essentially means 'Slashing Miner and the Power they delegated'
3. If Storage Power was increased: `CPP` does nothing.
4. `CPP` performs bookeeping, updating `MinerClaim`

### Delegating Power

#### Assumptions
- It's an **extrinsic**, can be called by a **Miner**.

#### Flow
1. **Miner** calls `CPP.delegate_power(miner: T::MinerId, collator: T::CollatorId, amount: T::StoragePower)`
2. `CPP` saves delegated **Storage Power** in **Miner's** claims (`staked_power`).
3. In the next **session**, the saved Power is picked up by `CSP`, by calling `CPP.get_collator_power(collator: T::CollatorId) -> T::StoragePower`.

#### Slashing Collator (?)

TODO: I don't have this piece of the puzzle yet. I mean... What happens if a **Miner** staked some power on a **Collator** and it misbehaved? Do we **slash** the **Miner's** staked tokens/or storage power, and if so, how?

[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.
th7nder marked this conversation as resolved.
Show resolved Hide resolved
[5]: https://github.com/eigerco/polka-disk/blob/main/doc/research/lotus/lotus-overview.md#Roles
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