diff --git a/Cargo.lock b/Cargo.lock index 7ff94208a..17f217f31 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7660,8 +7660,10 @@ dependencies = [ "frame-support", "frame-system", "log", + "pallet-balances", "parity-scale-codec", "scale-info", + "sp-arithmetic", "sp-core", "sp-io", "sp-runtime", diff --git a/pallets/storage-provider/Cargo.toml b/pallets/storage-provider/Cargo.toml index 1aed701eb..37713ad0f 100644 --- a/pallets/storage-provider/Cargo.toml +++ b/pallets/storage-provider/Cargo.toml @@ -1,5 +1,6 @@ [package] authors.workspace = true +description = "Handles storage provider registration and proofs" edition.workspace = true homepage.workspace = true license-file.workspace = true @@ -17,6 +18,8 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { workspace = true, default-features = false, features = ["derive"] } log.workspace = true scale-info = { workspace = true, default-features = false, features = ["derive"] } +sp-arithmetic = { workspace = true, default-features = false } +sp-core = { workspace = true, default-features = false } # Frame deps frame-benchmarking = { workspace = true, default-features = false, optional = true } @@ -24,7 +27,7 @@ frame-support = { workspace = true, default-features = false } frame-system = { workspace = true, default-features = false } [dev-dependencies] -sp-core = { workspace = true, default-features = false } +pallet-balances = { workspace = true, default-features = false } sp-io = { workspace = true } sp-runtime = { workspace = true, default-features = false } @@ -41,6 +44,7 @@ std = [ "frame-benchmarking?/std", "frame-support/std", "frame-system/std", + "pallet-balances/std", "scale-info/std", "sp-core/std", "sp-io/std", diff --git a/pallets/storage-provider/DESIGN.md b/pallets/storage-provider/DESIGN.md index 227844e70..df2cc02cc 100644 --- a/pallets/storage-provider/DESIGN.md +++ b/pallets/storage-provider/DESIGN.md @@ -13,11 +13,7 @@ - [Sector Penalty (SP)](#sector-penalty-sp) - [Termination Penalty (TP)](#termination-penalty-tp) - [State management for Storage Providers](#state-management-for-storage-providers) - - [Static information about a Storage Provider](#static-information-about-a-storage-provider) - [Sector sealing](#sector-sealing) - - [Data structures](#data-structures) - - [Proof of Spacetime](#proof-of-spacetime) - - [Proof of Replication](#proof-of-replication) - [Storage Provider Flow](#storage-provider-flow) - [Registration](#registration) - [Commit](#commit) @@ -81,40 +77,6 @@ By implementing these penalties, storage providers are incentivised to maintain In our parachain, the state management for all storage providers is handled collectively, unlike Filecoin, which manages the state for individual storage providers. -### Static information about a Storage Provider - -The below struct and its fields ensure that all necessary static information about a Storage provider is encapsulated, allowing for efficient management and interaction within the parachain. - -```rust -pub struct StorageProviderInfo { - /// Libp2p identity that should be used when connecting to this Storage Provider - pub peer_id: PeerId, - - /// The proof type used by this Storage provider for sealing sectors. - /// Rationale: Different StorageProviders may use different proof types for sealing sectors. By storing - /// the `window_post_proof_type`, we can ensure that the correct proof mechanisms are applied and verified - /// according to the provider's chosen method. This enhances compatibility and integrity in the proof-of-storage - /// processes. - pub window_post_proof_type: RegisteredPoStProof, - - /// Amount of space in each sector committed to the network by this Storage Provider - /// - /// Rationale: The `sector_size` indicates the amount of data each sector can hold. This information is crucial - /// for calculating storage capacity, economic incentives, and the validation process. It ensures that the storage - /// commitments made by the provider are transparent and verifiable. - pub sector_size: SectorSize, - - /// The number of sectors in each Window PoSt partition (proof). - /// This is computed from the proof type and represented here redundantly. - /// - /// Rationale: The `window_post_partition_sectors` field specifies the number of sectors included in each - /// Window PoSt proof partition. This redundancy ensures that partition calculations are consistent and - /// simplifies the process of generating and verifying proofs. By storing this value, we enhance the efficiency - /// of proof operations and reduce computational overhead during runtime. - pub window_post_partition_sectors: u64, -} -``` - ## Sector sealing Before a sector can be used, the storage provider must seal the sector, which involves encoding the data in the sector to prepare it for the proving process. @@ -132,65 +94,6 @@ Sealing a sector using Proof-of-Replication (PoRep) is a computation-intensive p - **Run a SNARK on the Proof**: Compress the proof using a Succinct Non-interactive Argument of Knowledge (SNARK). - **Submit the Compressed Proof:** Submit the result of the compression to the blockchain as certification of the storage commitment. -## Data structures - -### Proof of Spacetime - -> [!NOTE] -> For more information about proofs check out the [proof of storage docs](./PROOF-OF-STORAGE.md) - -Proof of Spacetime indicates the version and the sector size of the proof. This type is used by the Storage Provider when initially starting up to indicate what PoSt version it will use to submit Window PoSt proof. - -```rust -pub enum RegisteredPoStProof { - StackedDRGWindow2KiBV1P1, -} -``` - -The `SectorSize` indicates one of a set of possible sizes in the network. - -```rust -#[repr(u64)] -pub enum SectorSize { - _2KiB, -} -``` - -The `PoStProof` is the proof of spacetime data that is stored on chain - -```rust -pub struct PoStProof { - pub post_proof: RegisteredPoStProof, - pub proof_bytes: Vec, -} -``` - -### Proof of Replication - -> [!NOTE] -> For more information about proofs check out the [proof of storage docs](./PROOF-OF-STORAGE.md) - -Proof of Replication is used when a Storage Provider wants to store data on behalf of a client and receives a piece of client data. The data will first be placed in a sector after which that sector is sealed by the storage provider. Then a unique encoding, which serves as proof that the Storage Provider has replicated a copy of the data they agreed to store, is generated. Finally, the proof is compressed and submitted to the network as certification of storage. - -```rust -/// This type indicates the seal proof type which defines the version and the sector size -pub enum RegisteredSealProof { - StackedDRG2KiBV1P1, -} -``` - -The unique encoding created during the sealing process is generated using the sealed data, the storage provider who seals the data and the time at which the data was sealed. - -```rust -/// This type is passed into the pre commit function on the storage provider pallet -pub struct SectorPreCommitInfo { - pub seal_proof: RegisteredSealProof, - pub sector_number: SectorNumber, - pub sealed_cid: Cid, - pub expiration: u64, -} -``` - ## Storage Provider Flow ### Registration @@ -211,4 +114,4 @@ When the storage provider has completed their PoSt, they must submit it to the n ## Storage provider pallet hooks -Substrate pallet hooks execute some actions when certain conditions are met. We use these hooks, when a block finalizes, to check if storage providers are up to date with their proofs. If a proof needs to be submitted but isn't the storage provider pallet will penalize the storage provider accordingly [slash](#storage-fault-slashing) their collateral that the locked up during the [pre commit section](#commit). \ No newline at end of file +Substrate pallet hooks execute some actions when certain conditions are met. We use these hooks, when a block finalizes, to check if storage providers are up to date with their proofs. If a proof needs to be submitted but isn't the storage provider pallet will penalize the storage provider accordingly [slash](#storage-fault-slashing) their collateral that the locked up during the [pre commit section](#commit). diff --git a/pallets/storage-provider/src/lib.rs b/pallets/storage-provider/src/lib.rs index ed4dd7465..6e2fedd71 100644 --- a/pallets/storage-provider/src/lib.rs +++ b/pallets/storage-provider/src/lib.rs @@ -10,23 +10,50 @@ //! The Storage Provider Pallet is the source of truth for anything storage provider related. #![cfg_attr(not(feature = "std"), no_std)] +pub use pallet::{Config, Pallet}; +use scale_info::prelude::string::String; #[cfg(feature = "runtime-benchmarks")] mod benchmarks; -mod types; +#[cfg(test)] +mod mock; -pub use pallet::{Config, Pallet}; +#[cfg(test)] +mod test; + +mod proofs; +mod sector; +mod storage_provider; + +type Cid = String; #[frame_support::pallet(dev_mode)] pub mod pallet { use core::fmt::Debug; use codec::{Decode, Encode}; - use frame_support::pallet_prelude::{IsType, StorageMap}; + use frame_support::{ + dispatch::DispatchResultWithPostInfo, + ensure, + pallet_prelude::*, + traits::{Currency, ReservableCurrency}, + }; + use frame_system::{ensure_signed, pallet_prelude::*, Config as SystemConfig}; use scale_info::TypeInfo; - use crate::types::StorageProviderInfo; + use crate::{ + proofs::{ + assign_proving_period_offset, current_deadline_index, current_proving_period_start, + RegisteredPoStProof, + }, + storage_provider::{StorageProviderInfo, StorageProviderState}, + }; + + /// Allows to extract Balance of an account via the Config::Currency associated type. + /// BalanceOf is a sophisticated way of getting an u128. + type BalanceOf = + <::Currency as Currency<::AccountId>>::Balance; #[pallet::pallet] #[pallet::without_storage_info] // Allows to define storage items without fixed size @@ -42,21 +69,91 @@ pub mod pallet { /// https://github.com/libp2p/specs/blob/2ea41e8c769f1bead8e637a9d4ebf8c791976e8a/peer-ids/peer-ids.md#peer-ids /// More information about libp2p peer ids: https://docs.libp2p.io/concepts/fundamentals/peers/ type PeerId: Clone + Debug + Decode + Encode + Eq + TypeInfo; + + /// Currency mechanism, used for collateral + type Currency: ReservableCurrency; + + #[pallet::constant] // put the constant in metadata + /// Proving period for submitting Window PoSt, 24 hours is blocks + type WPoStProvingPeriod: Get>; + + #[pallet::constant] // put the constant in metadata + /// Window PoSt challenge window (default 30 minutes in blocks) + type WPoStChallengeWindow: Get>; } - // Need some storage type that keeps track of sectors, deadlines and terminations. - // Could be added to this type maybe? + /// Need some storage type that keeps track of sectors, deadlines and terminations. #[pallet::storage] #[pallet::getter(fn storage_providers)] - pub type StorageProviders = - StorageMap<_, _, T::AccountId, StorageProviderInfo>; + pub type StorageProviders = StorageMap< + _, + _, + T::AccountId, + StorageProviderState, BlockNumberFor>, + >; #[pallet::event] - pub enum Event {} + #[pallet::generate_deposit(fn deposit_event)] + pub enum Event { + /// Emitted when a new storage provider is registered. + StorageProviderRegistered { + owner: T::AccountId, + info: StorageProviderInfo, + }, + } #[pallet::error] - pub enum Error {} + pub enum Error { + /// Emitted when a storage provider is trying to be registered + /// but there is already storage provider registered for that `AccountId`. + StorageProviderExists, + /// Emitted when a type conversion fails. + ConversionError, + } #[pallet::call] - impl Pallet {} + impl Pallet { + pub fn register_storage_provider( + origin: OriginFor, + peer_id: T::PeerId, + window_post_proof_type: RegisteredPoStProof, + ) -> DispatchResultWithPostInfo { + // Check that the extrinsic was signed and get the signer + // This will be the owner of the storage provider + let owner = ensure_signed(origin)?; + + // Ensure that the storage provider does not exist yet + ensure!( + !StorageProviders::::contains_key(&owner), + Error::::StorageProviderExists + ); + + let proving_period = T::WPoStProvingPeriod::get(); + + let current_block = >::block_number(); + + let offset = assign_proving_period_offset::>( + &owner, + current_block, + proving_period, + ) + .map_err(|_| Error::::ConversionError)?; + + let period_start = current_proving_period_start(current_block, offset, proving_period); + + let deadline_idx = + current_deadline_index(current_block, period_start, T::WPoStChallengeWindow::get()); + + let info = StorageProviderInfo::new(peer_id, window_post_proof_type); + + let state = StorageProviderState::new(&info, period_start, deadline_idx); + + StorageProviders::::insert(&owner, state); + + // Emit event + Self::deposit_event(Event::StorageProviderRegistered { owner, info }); + + Ok(().into()) + } + } } diff --git a/pallets/storage-provider/src/mock.rs b/pallets/storage-provider/src/mock.rs new file mode 100644 index 000000000..e80445af6 --- /dev/null +++ b/pallets/storage-provider/src/mock.rs @@ -0,0 +1,80 @@ +use frame_support::{ + derive_impl, pallet_prelude::ConstU32, parameter_types, sp_runtime::BoundedVec, +}; +use sp_runtime::BuildStorage; + +use crate as pallet_storage_provider; + +type Block = frame_system::mocking::MockBlock; + +type BlockNumber = u32; + +const MILLISECS_PER_BLOCK: u64 = 12000; +const SLOT_DURATION: u64 = MILLISECS_PER_BLOCK; +const MINUTES: BlockNumber = 60_000 / (MILLISECS_PER_BLOCK as BlockNumber); +const HOURS: BlockNumber = MINUTES * 60; +const DAYS: BlockNumber = HOURS * 24; + +frame_support::construct_runtime!( + pub enum Test { + System: frame_system, + Balances: pallet_balances, + StorageProvider: pallet_storage_provider::pallet, + } +); + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] +impl frame_system::Config for Test { + type Block = Block; + type AccountData = pallet_balances::AccountData; +} + +#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)] +impl pallet_balances::Config for Test { + type AccountStore = System; +} + +parameter_types! { + pub const WpostProvingPeriod: BlockNumber = DAYS; + // Half an hour (=48 per day) + // 30 * 60 = 30 minutes + // SLOT_DURATION is in milliseconds thats why we / 1000 + pub const WpostChallengeWindow: BlockNumber = 30 * 60 / (SLOT_DURATION as BlockNumber / 1000); +} + +impl pallet_storage_provider::Config for Test { + type RuntimeEvent = RuntimeEvent; + type PeerId = BoundedVec>; // Arbitrary length + type Currency = Balances; + type WPoStProvingPeriod = WpostProvingPeriod; + type WPoStChallengeWindow = WpostChallengeWindow; +} + +pub const ALICE: u64 = 0; +pub const BOB: u64 = 1; +pub const INITIAL_FUNDS: u64 = 100; + +// Build genesis storage according to the mock runtime. +pub fn new_test_ext() -> sp_io::TestExternalities { + let mut t = frame_system::GenesisConfig::::default() + .build_storage() + .unwrap() + .into(); + pallet_balances::GenesisConfig:: { + balances: vec![(ALICE, INITIAL_FUNDS), (BOB, INITIAL_FUNDS)], + } + .assimilate_storage(&mut t) + .unwrap(); + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| System::set_block_number(1)); + ext +} + +pub fn events() -> Vec { + let evt = System::events() + .into_iter() + .map(|evt| evt.event) + .collect::>(); + System::reset_events(); + evt +} diff --git a/pallets/storage-provider/src/proofs.rs b/pallets/storage-provider/src/proofs.rs new file mode 100644 index 000000000..88aff8a82 --- /dev/null +++ b/pallets/storage-provider/src/proofs.rs @@ -0,0 +1,123 @@ +use codec::{Decode, Encode}; +use frame_support::{pallet_prelude::ConstU32, sp_runtime::BoundedVec}; +use scale_info::TypeInfo; +use sp_arithmetic::traits::BaseArithmetic; +use sp_core::blake2_64; + +use crate::sector::SectorSize; + +/// Proof of Spacetime type, indicating version and sector size of the proof. +#[derive(Debug, Decode, Encode, TypeInfo, PartialEq, Eq, Clone, Copy)] +pub enum RegisteredPoStProof { + StackedDRGWindow2KiBV1P1, +} + +impl RegisteredPoStProof { + /// Returns the sector size of the proof type, which is measured in bytes. + pub fn sector_size(self) -> SectorSize { + match self { + RegisteredPoStProof::StackedDRGWindow2KiBV1P1 => SectorSize::_2KiB, + } + } + + /// Returns the partition size, in sectors, associated with a proof type. + /// The partition size is the number of sectors proven in a single PoSt proof. + pub fn window_post_partitions_sector(self) -> u64 { + // Resolve to post proof and then compute size from that. + + match self { + RegisteredPoStProof::StackedDRGWindow2KiBV1P1 => 2, + } + } +} + +/// Proof of Spacetime data stored on chain. +#[derive(Debug, Decode, Encode, TypeInfo, PartialEq, Eq, Clone)] +pub struct PoStProof { + pub post_proof: RegisteredPoStProof, + pub proof_bytes: BoundedVec>, // Arbitrary length +} + +/// Seal proof type which defines the version and sector size. +#[allow(non_camel_case_types)] +#[derive(Debug, Decode, Encode, TypeInfo, Eq, PartialEq, Clone)] +pub enum RegisteredSealProof { + StackedDRG2KiBV1P1, +} + +#[derive(Debug)] +pub enum ProofError { + Conversion, +} + +/// Assigns proving period offset randomly in the range [0, WPOST_PROVING_PERIOD) +/// by hashing the address and current block number. +/// +/// Filecoin implementation reference: +pub fn assign_proving_period_offset( + addr: &AccountId, + current_block: BlockNumber, + wpost_proving_period: BlockNumber, +) -> Result +where + AccountId: Encode, + BlockNumber: BaseArithmetic + Encode + TryFrom, +{ + // Encode address and current block number + let mut addr = addr.encode(); + let mut block_num = current_block.encode(); + + // Concatenate the encoded block number to the encoded address. + addr.append(&mut block_num); + + // Hash the address and current block number for a pseudo-random offset. + let digest = blake2_64(&addr); + + // Create a pseudo-random offset from the bytes of the hash of the address and current block number. + let offset = u64::from_be_bytes(digest); + + // Convert into block number + let mut offset = + TryInto::::try_into(offset).map_err(|_| ProofError::Conversion)?; + + // Mod with the proving period so it is within the valid range of [0, WPOST_PROVING_PERIOD) + offset %= wpost_proving_period; + + Ok(offset) +} + +/// Computes the epoch at which a proving period should start such that it is greater than the current epoch, and +/// has a defined offset from being an exact multiple of WPoStProvingPeriod. +/// A miner is exempt from Window PoSt until the first full proving period starts. +// +/// Filecoin implementation reference: https://github.com/filecoin-project/builtin-actors/blob/17ede2b256bc819dc309edf38e031e246a516486/actors/miner/src/lib.rs#L4907 +pub fn current_proving_period_start( + current_block: BlockNumber, + offset: BlockNumber, + proving_period: BlockNumber, +) -> BlockNumber +where + BlockNumber: BaseArithmetic, +{ + let curr_modulus = current_block.clone() % proving_period.clone(); + + let period_progress = if curr_modulus >= offset { + curr_modulus - offset + } else { + proving_period - (offset - curr_modulus) + }; + + current_block - period_progress +} + +/// Filecoin implementation reference: https://github.com/filecoin-project/builtin-actors/blob/17ede2b256bc819dc309edf38e031e246a516486/actors/miner/src/lib.rs#L4923 +pub fn current_deadline_index( + current_block: BlockNumber, + period_start: BlockNumber, + challenge_window: BlockNumber, +) -> BlockNumber +where + BlockNumber: BaseArithmetic, +{ + (current_block - period_start) / challenge_window +} diff --git a/pallets/storage-provider/src/sector.rs b/pallets/storage-provider/src/sector.rs new file mode 100644 index 000000000..36d22bef0 --- /dev/null +++ b/pallets/storage-provider/src/sector.rs @@ -0,0 +1,54 @@ +use codec::{Decode, Encode}; +use scale_info::TypeInfo; + +use crate::{proofs::RegisteredSealProof, Cid}; + +// https://github.com/filecoin-project/builtin-actors/blob/17ede2b256bc819dc309edf38e031e246a516486/runtime/src/runtime/policy.rs#L262 +pub const SECTORS_MAX: u32 = 32 << 20; + +/// SectorNumber is a numeric identifier for a sector. +pub type SectorNumber = u64; + +/// SectorSize indicates one of a set of possible sizes in the network. +#[derive(Encode, Decode, TypeInfo, Clone, Debug, PartialEq, Eq, Copy)] +pub enum SectorSize { + _2KiB, +} + +/// This type is passed into the pre commit function on the storage provider pallet +#[derive(Debug, Decode, Encode, TypeInfo)] +pub struct SectorPreCommitInfo { + /// The proof type this SP will use for the pre-comitted sectors. + pub seal_proof: RegisteredSealProof, + /// Which sector number this SP is pre-committing. + pub sector_number: SectorNumber, + /// 'commR' Commitment of replication, + /// Some docs on commR here: + pub sealed_cid: Cid, + /// Expiration of the pre-committed sector. + pub expiration: u64, +} + +/// Information stored on-chain for a pre-committed sector. +#[derive(Debug, Decode, Encode, TypeInfo)] +pub struct SectorPreCommitOnChainInfo { + pub info: SectorPreCommitInfo, + /// Total collateral for this sector + pub pre_commit_deposit: Balance, + /// Block number this was pre-committed + pub pre_commit_block_number: BlockNumber, +} + +#[derive(Debug, Decode, Encode, TypeInfo)] +pub struct SectorOnChainInfo { + pub sector_number: SectorNumber, + /// The seal proof type implies the PoSt proofs + pub seal_proof: RegisteredSealProof, + /// The root hash of the sealed sector's merkle tree. + /// Also called CommR, or 'replica commitment'. + pub sealed_cid: Cid, + /// Block number during which the sector proof was accepted + pub activation: BlockNumber, + /// Block number during which the sector expires + pub expiration: BlockNumber, +} diff --git a/pallets/storage-provider/src/storage_provider.rs b/pallets/storage-provider/src/storage_provider.rs new file mode 100644 index 000000000..7e8d700c1 --- /dev/null +++ b/pallets/storage-provider/src/storage_provider.rs @@ -0,0 +1,107 @@ +use codec::{Decode, Encode}; +use frame_support::{pallet_prelude::ConstU32, sp_runtime::BoundedVec}; +use scale_info::TypeInfo; + +use crate::{ + proofs::RegisteredPoStProof, + sector::{SectorOnChainInfo, SectorPreCommitOnChainInfo, SectorSize, SECTORS_MAX}, +}; + +/// This struct holds the state of a single storage provider. +#[derive(Debug, Decode, Encode, TypeInfo)] +pub struct StorageProviderState { + /// Contains static information about this storage provider + pub info: StorageProviderInfo, + + /// Information for all proven and not-yet-garbage-collected sectors. + pub sectors: BoundedVec, ConstU32>, + + /// Total funds locked as pre_commit_deposit + /// Optional because when registering there is no need for deposits. + pub pre_commit_deposits: Option, + + /// Sectors that have been pre-committed but not yet proven. + pub pre_committed_sectors: + BoundedVec, ConstU32>, + + /// The first block in this storage provider's current proving period. This is the first block in which a PoSt for a + /// partition at the storage provider's first deadline may arrive. Alternatively, it is after the last block at which + /// a PoSt for the previous window is valid. + /// Always greater than zero, this may be greater than the current block for genesis miners in the first + /// WPoStProvingPeriod blocks of the chain; the blocks before the first proving period starts are exempt from Window + /// PoSt requirements. + /// Updated at the end of every period. + pub proving_period_start: BlockNumber, + + /// Index of the deadline within the proving period beginning at ProvingPeriodStart that has not yet been + /// finalized. + /// Updated at the end of each deadline window. + pub current_deadline: BlockNumber, +} + +impl StorageProviderState +where + PeerId: Clone + Decode + Encode + TypeInfo, + BlockNumber: Decode + Encode + TypeInfo, +{ + pub fn new( + info: &StorageProviderInfo, + period_start: BlockNumber, + deadline_idx: BlockNumber, + ) -> Self { + Self { + info: info.clone(), + sectors: BoundedVec::new(), + pre_commit_deposits: None, + pre_committed_sectors: BoundedVec::new(), + proving_period_start: period_start, + current_deadline: deadline_idx, + } + } +} + +/// Static information about the storage provider. +#[derive(Debug, Clone, Copy, Decode, Encode, TypeInfo, PartialEq)] +pub struct StorageProviderInfo { + /// Libp2p identity that should be used when connecting to this Storage Provider + pub peer_id: PeerId, + + /// The proof type used by this Storage provider for sealing sectors. + /// Rationale: Different StorageProviders may use different proof types for sealing sectors. By storing + /// the `window_post_proof_type`, we can ensure that the correct proof mechanisms are applied and verified + /// according to the provider's chosen method. This enhances compatibility and integrity in the proof-of-storage + /// processes. + pub window_post_proof_type: RegisteredPoStProof, + + /// Amount of space in each sector committed to the network by this Storage Provider + /// + /// Rationale: The `sector_size` indicates the amount of data each sector can hold. This information is crucial + /// for calculating storage capacity, economic incentives, and the validation process. It ensures that the storage + /// commitments made by the provider are transparent and verifiable. + pub sector_size: SectorSize, + + /// The number of sectors in each Window PoSt partition (proof). + /// This is computed from the proof type and represented here redundantly. + /// + /// Rationale: The `window_post_partition_sectors` field specifies the number of sectors included in each + /// Window PoSt proof partition. This redundancy ensures that partition calculations are consistent and + /// simplifies the process of generating and verifying proofs. By storing this value, we enhance the efficiency + /// of proof operations and reduce computational overhead during runtime. + pub window_post_partition_sectors: u64, +} + +impl StorageProviderInfo { + /// Create a new instance of StorageProviderInfo + pub fn new(peer_id: PeerId, window_post_proof_type: RegisteredPoStProof) -> Self { + let sector_size = window_post_proof_type.sector_size(); + + let window_post_partition_sectors = window_post_proof_type.window_post_partitions_sector(); + + Self { + peer_id, + window_post_proof_type, + sector_size, + window_post_partition_sectors, + } + } +} diff --git a/pallets/storage-provider/src/test.rs b/pallets/storage-provider/src/test.rs new file mode 100644 index 000000000..22f41610b --- /dev/null +++ b/pallets/storage-provider/src/test.rs @@ -0,0 +1,94 @@ +use frame_support::{assert_noop, assert_ok, sp_runtime::BoundedVec}; + +use crate::{ + mock::{events, new_test_ext, RuntimeEvent, RuntimeOrigin, StorageProvider, Test, ALICE, BOB}, + pallet::{Error, Event, StorageProviders}, + proofs::RegisteredPoStProof, + storage_provider::StorageProviderInfo, +}; + +#[test] +fn initial_state() { + new_test_ext().execute_with(|| { + assert!(!StorageProviders::::contains_key(ALICE)); + assert!(!StorageProviders::::contains_key(BOB)); + }) +} + +/// Tests if storage provider registration is successful. +#[test] +fn register_sp() { + new_test_ext().execute_with(|| { + let peer_id = "storage_provider_1".as_bytes().to_vec(); + let peer_id = BoundedVec::try_from(peer_id).unwrap(); + let window_post_type = RegisteredPoStProof::StackedDRGWindow2KiBV1P1; + let expected_sector_size = window_post_type.sector_size(); + let expected_partition_sectors = window_post_type.window_post_partitions_sector(); + let expected_sp_info = StorageProviderInfo::new(peer_id.clone(), window_post_type); + + // Register BOB as a storage provider. + assert_ok!(StorageProvider::register_storage_provider( + RuntimeOrigin::signed(BOB), + peer_id.clone(), + window_post_type, + )); + assert!(StorageProviders::::contains_key(BOB)); + // `unwrap()` should be safe because of the above check. + let sp_bob = StorageProviders::::get(BOB).unwrap(); + + // Check that storage provider information is correct. + assert_eq!(sp_bob.info.peer_id, peer_id); + assert_eq!(sp_bob.info.window_post_proof_type, window_post_type); + assert_eq!(sp_bob.info.sector_size, expected_sector_size); + assert_eq!( + sp_bob.info.window_post_partition_sectors, + expected_partition_sectors + ); + + // Check that pre commit sectors are empty. + assert!(sp_bob.pre_committed_sectors.is_empty()); + // Check that no pre commit deposit is made + assert!(sp_bob.pre_commit_deposits.is_none()); + // Check that sectors are empty. + assert!(sp_bob.sectors.is_empty()); + + // Check that the event triggered + assert_eq!( + events(), + [RuntimeEvent::StorageProvider( + Event::::StorageProviderRegistered { + owner: BOB, + info: expected_sp_info + } + )] + ) + }) +} + +/// Check that double registration fails +#[test] +fn double_register_sp() { + new_test_ext().execute_with(|| { + let peer_id = "storage_provider_1".as_bytes().to_vec(); + let peer_id = BoundedVec::try_from(peer_id).unwrap(); + let window_post_type = RegisteredPoStProof::StackedDRGWindow2KiBV1P1; + + // Register BOB as a storage provider. + assert_ok!(StorageProvider::register_storage_provider( + RuntimeOrigin::signed(BOB), + peer_id.clone(), + window_post_type, + )); + assert!(StorageProviders::::contains_key(BOB)); + + // Try to register BOB again. Should fail + assert_noop!( + StorageProvider::register_storage_provider( + RuntimeOrigin::signed(BOB), + peer_id.clone(), + window_post_type, + ), + Error::::StorageProviderExists + ); + }); +} diff --git a/pallets/storage-provider/src/types.rs b/pallets/storage-provider/src/types.rs deleted file mode 100644 index a9c966177..000000000 --- a/pallets/storage-provider/src/types.rs +++ /dev/null @@ -1,150 +0,0 @@ -use codec::{Decode, Encode}; -use scale_info::{ - prelude::{string::String, vec::Vec}, - TypeInfo, -}; - -/// SectorNumber is a numeric identifier for a sector. -pub type SectorNumber = u64; - -/// Content identifier -pub type Cid = String; - -#[derive(Decode, Encode, TypeInfo)] -pub struct StorageProviderInfo< - AccountId: Encode + Decode + Eq + PartialEq, - PeerId: Encode + Decode + Eq + PartialEq, -> { - /// Account that owns this StorageProvider - /// - Income and returned collateral are paid to this address - /// - /// Rationale: The owner account is essential for economic transactions and permissions management. - /// By tying the income and collateral to this address, we ensure that the economic benefits and responsibilities - /// are correctly attributed. - pub owner: AccountId, - - /// Libp2p identity that should be used when connecting to this Storage Provider - pub peer_id: PeerId, - - /// The proof type used by this Storage provider for sealing sectors. - /// Rationale: Different StorageProviders may use different proof types for sealing sectors. By storing - /// the `window_post_proof_type`, we can ensure that the correct proof mechanisms are applied and verified - /// according to the provider's chosen method. This enhances compatibility and integrity in the proof-of-storage - /// processes. - pub window_post_proof_type: RegisteredPoStProof, - - /// Amount of space in each sector committed to the network by this Storage Provider - /// - /// Rationale: The `sector_size` indicates the amount of data each sector can hold. This information is crucial - /// for calculating storage capacity, economic incentives, and the validation process. It ensures that the storage - /// commitments made by the provider are transparent and verifiable. - pub sector_size: SectorSize, - - /// The number of sectors in each Window PoSt partition (proof). - /// This is computed from the proof type and represented here redundantly. - /// - /// Rationale: The `window_post_partition_sectors` field specifies the number of sectors included in each - /// Window PoSt proof partition. This redundancy ensures that partition calculations are consistent and - /// simplifies the process of generating and verifying proofs. By storing this value, we enhance the efficiency - /// of proof operations and reduce computational overhead during runtime. - pub window_post_partition_sectors: u64, -} - -impl StorageProviderInfo -where - AccountId: Encode + Decode + Eq + PartialEq, - PeerId: Encode + Decode + Eq + PartialEq + Clone, -{ - /// Create a new instance of StorageProviderInfo - pub fn new( - owner: AccountId, - peer_id: PeerId, - window_post_proof_type: RegisteredPoStProof, - ) -> Result { - let sector_size = window_post_proof_type.sector_size(); - - let window_post_partition_sectors = window_post_proof_type.window_post_partitions_sector(); - - Ok(Self { - owner, - peer_id, - window_post_proof_type, - sector_size, - window_post_partition_sectors, - }) - } - - /// Updates the owner address. - pub fn change_owner(&self, owner: AccountId) -> Self { - Self { - owner, - peer_id: self.peer_id.clone(), - window_post_proof_type: self.window_post_proof_type, - sector_size: self.sector_size, - window_post_partition_sectors: self.window_post_partition_sectors, - } - } -} - -/// SectorSize indicates one of a set of possible sizes in the network. -#[derive(Encode, Decode, TypeInfo, Clone, Debug, PartialEq, Eq, Copy)] -pub enum SectorSize { - _2KiB, -} - -/// Proof of Spacetime type, indicating version and sector size of the proof. -#[derive(Debug, Decode, Encode, TypeInfo, PartialEq, Eq, Clone, Copy)] -pub enum RegisteredPoStProof { - StackedDRGWindow2KiBV1P1, -} - -impl RegisteredPoStProof { - /// Returns the sector size of the proof type, which is measured in bytes. - pub fn sector_size(self) -> SectorSize { - use RegisteredPoStProof::*; - match self { - StackedDRGWindow2KiBV1P1 => SectorSize::_2KiB, - } - } - - /// Proof size for each PoStProof type - #[allow(unused)] - pub fn proof_size(self) -> usize { - use RegisteredPoStProof::*; - match self { - StackedDRGWindow2KiBV1P1 => 192, - } - } - /// Returns the partition size, in sectors, associated with a proof type. - /// The partition size is the number of sectors proven in a single PoSt proof. - pub fn window_post_partitions_sector(self) -> u64 { - // Resolve to post proof and then compute size from that. - use RegisteredPoStProof::*; - match self { - StackedDRGWindow2KiBV1P1 => 2, - } - } -} - -/// Proof of Spacetime data stored on chain. -#[derive(Debug, Decode, Encode, TypeInfo, PartialEq, Eq, Clone)] -pub struct PoStProof { - pub post_proof: RegisteredPoStProof, - pub proof_bytes: Vec, -} - -/// Seal proof type which defines the version and sector size. -#[allow(non_camel_case_types)] -#[derive(Debug, Decode, Encode, TypeInfo, Eq, PartialEq, Clone)] -pub enum RegisteredSealProof { - StackedDRG2KiBV1P1, -} - -/// This type is passed into the pre commit function on the storage provider pallet -#[derive(Debug, Decode, Encode, TypeInfo, Eq, PartialEq, Clone)] -pub struct SectorPreCommitInfo { - pub seal_proof: RegisteredSealProof, - pub sector_number: SectorNumber, - pub sealed_cid: Cid, - pub expiration: u64, -} diff --git a/runtime/src/configs/mod.rs b/runtime/src/configs/mod.rs index 013733c17..aaf6bd102 100644 --- a/runtime/src/configs/mod.rs +++ b/runtime/src/configs/mod.rs @@ -34,7 +34,7 @@ use frame_support::{ parameter_types, traits::{ConstBool, ConstU32, ConstU64, ConstU8, EitherOfDiverse, TransformOrigin}, weights::{ConstantMultiplier, Weight}, - PalletId, + BoundedVec, PalletId, }; use frame_system::{ limits::{BlockLength, BlockWeights}, @@ -45,7 +45,6 @@ use parachains_common::message_queue::{NarrowOriginToSibling, ParaIdToSibling}; use polkadot_runtime_common::{ xcm_sender::NoPriceForMessageDelivery, BlockHashCount, SlowAdjustingFeeUpdate, }; -use scale_info::prelude::vec::Vec; use sp_consensus_aura::sr25519::AuthorityId as AuraId; use sp_runtime::{traits::Verify, MultiSignature, Perbill}; use sp_version::RuntimeVersion; @@ -307,9 +306,20 @@ impl pallet_collator_selection::Config for Runtime { type WeightInfo = (); } +parameter_types! { + pub const WpostProvingPeriod: BlockNumber = DAYS; + // Half an hour (=48 per day) + // 30 * 60 = 30 minutes + // SLOT_DURATION is in milliseconds thats why we / 1000 + pub const WpostChallengeWindow: BlockNumber = 30 * 60 / (SLOT_DURATION as BlockNumber / 1000); +} + impl pallet_storage_provider::Config for Runtime { type RuntimeEvent = RuntimeEvent; - type PeerId = Vec; + type PeerId = BoundedVec>; // Arbitrary length + type Currency = Balances; + type WPoStProvingPeriod = WpostProvingPeriod; + type WPoStChallengeWindow = WpostChallengeWindow; } parameter_types! {