Skip to content

Commit

Permalink
WIP: Add deadline and partition data types
Browse files Browse the repository at this point in the history
  • Loading branch information
aidan46 committed Jul 15, 2024
1 parent ee342d2 commit f79e4f5
Show file tree
Hide file tree
Showing 7 changed files with 474 additions and 21 deletions.
174 changes: 174 additions & 0 deletions pallets/storage-provider/src/deadline.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
use codec::{Decode, Encode};
use frame_support::{
pallet_prelude::*,
sp_runtime::{BoundedBTreeMap, BoundedVec},
PalletError,
};
use scale_info::TypeInfo;

use crate::partition::{Partition, PartitionNumber, MAX_PARTITIONS};

type DeadlineResult<T> = Result<T, DeadlineError>;

/// Deadline holds the state for all sectors due at a specific deadline.
///
/// A deadline exists along side 47 other deadlines (1 for every 30 minutes in a day).
/// Only one deadline may be active for a given proving window.
#[derive(Clone, Debug, Decode, Encode, PartialEq, TypeInfo)]
pub struct Deadline<BlockNumber> {
/// Partitions in this deadline. Indexed on partition number.
pub partitions:
BoundedBTreeMap<PartitionNumber, Partition<BlockNumber>, ConstU32<MAX_PARTITIONS>>,

/// Partition numbers with sectors that terminated early.
pub early_terminations: BoundedBTreeSet<PartitionNumber, ConstU32<MAX_PARTITIONS>>,

/// The number of non-terminated sectors in this deadline (incl faulty).
pub live_sectors: u64,

/// The total number of sectors in this deadline (incl dead).
pub total_sectors: u64,
}

impl<BlockNumber> Deadline<BlockNumber> {
pub fn new() -> Self {
Self {
partitions: BoundedBTreeMap::new(),
early_terminations: BoundedBTreeSet::new(),
live_sectors: 0,
total_sectors: 0,
}
}

pub fn update_deadline(&mut self, new_dl: Self) {
self.early_terminations = new_dl.early_terminations;
self.live_sectors = new_dl.live_sectors;
self.total_sectors = new_dl.total_sectors;
self.partitions = new_dl.partitions;
}

/// Processes a series of PoSts, recording proven partitions and marking skipped
/// sectors as faulty.
pub fn record_proven_sectors(&mut self) {
todo!()
}
}

#[derive(Clone, Debug, Decode, Encode, PartialEq, TypeInfo)]
pub struct Deadlines<BlockNumber> {
/// Deadlines indexed by their proving periods — e.g. for proving period 7, find it in
/// `deadlines[7]` — proving periods are present in the interval `[0, 47]`.
///
/// Bounded to 48 elements since that's the set amount of deadlines per proving period.
///
/// In the original implementation, the information is kept in a separated structure, possibly
/// to make fetching the state more efficient as this is kept in the storage providers
/// blockstore. However, we're keeping all the state on-chain
///
/// References:
/// * <https://github.com/filecoin-project/builtin-actors/blob/17ede2b256bc819dc309edf38e031e246a516486/actors/miner/src/state.rs#L105-L108>
/// * <https://spec.filecoin.io/#section-algorithms.pos.post.constants--terminology>
/// * <https://spec.filecoin.io/#section-algorithms.pos.post.design>
pub due: BoundedVec<Deadline<BlockNumber>, ConstU32<48>>,
}

impl<BlockNumber> Deadlines<BlockNumber> {
/// Constructor function.
pub fn new() -> Self {
Self {
due: BoundedVec::new(),
}
}

/// Get the amount of deadlines that are due.
pub fn len(&self) -> usize {
self.due.len()
}

/// Inserts a new deadline.
/// Fails if the deadline insertion fails.
/// Returns the deadline index it inserted the deadline at
///
/// I am not sure if this should just insert the new deadline at the back and return the index
/// or take in the index and insert the deadline in there.
pub fn insert_deadline(
&mut self,
new_deadline: Deadline<BlockNumber>,
) -> DeadlineResult<usize> {
self.due
.try_push(new_deadline)
.map_err(|_| DeadlineError::CouldNotInsertDeadline)?;
// No underflow if the above was successful, minimum length 1
Ok(self.due.len() - 1)
}

/// Loads a deadline from the given index.
/// Fails if the index does not exist or is out of range.
pub fn load_deadline(&self, idx: usize) -> DeadlineResult<&Deadline<BlockNumber>> {
// Ensure the provided index is within range.
ensure!(self.len() > idx, DeadlineError::DeadlineIndexOutOfRange);
self.due.get(idx).ok_or(DeadlineError::DeadlineNotFound)
}

/// Updates a deadline at the given index.
pub fn update_deadline(
&mut self,
idx: usize,
new_deadline: Deadline<BlockNumber>,
) -> DeadlineResult<()> {
let dl = self
.due
.get_mut(idx)
.ok_or(DeadlineError::DeadlineNotFound)?;
dl.update_deadline(new_deadline);

Ok(())
}
}

#[derive(Clone, Debug, Decode, Encode, PartialEq, TypeInfo)]
pub struct DeadlineInfo<BlockNumber> {
/// The block number at which this info was calculated.
pub block_number: BlockNumber,

/// The block number at which the proving period for this deadline starts.
pub period_start: BlockNumber,

/// The deadline index within its proving window.
pub deadline_idx: u32,

/// The first block number from which a proof can be submitted.
pub open: BlockNumber,

/// The first block number from which a proof can *no longer* be submitted.
pub close: BlockNumber,
}

#[derive(Decode, Encode, PalletError, TypeInfo, RuntimeDebug)]
pub enum DeadlineError {
/// Emitted when the passed in deadline index supplied for `submit_windowed_post` is out of range.
DeadlineIndexOutOfRange,
/// Emitted when a trying to get a deadline index but fails because that index does not exist.
DeadlineNotFound,
/// Emitted when a given index in `Deadlines` already exists and try to insert a deadline on that index.
DeadlineIndexExists,
/// Emitted when trying to insert a new deadline fails.
CouldNotInsertDeadline,
}

#[cfg(test)]
mod test {
use super::*;

#[test]
fn update_and_load_deadline() -> DeadlineResult<()> {
let mut dls: Deadlines<u32> = Deadlines::new();
let dl: Deadline<u32> = Deadline::new();

let idx = dls.insert_deadline(dl.clone())?;
dls.update_deadline(idx, dl.clone())?;
let loaded_dl = dls.load_deadline(idx)?;
assert_eq!(&dl, loaded_dl);
Ok(())
}
}
104 changes: 92 additions & 12 deletions pallets/storage-provider/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ mod mock;
#[cfg(test)]
mod test;

mod deadline;
mod partition;
mod proofs;
mod sector;
mod storage_provider;
Expand Down Expand Up @@ -53,7 +55,7 @@ pub mod pallet {
},
sector::{
ProveCommitSector, SectorOnChainInfo, SectorPreCommitInfo, SectorPreCommitOnChainInfo,
SECTORS_MAX,
MAX_SECTORS,
},
storage_provider::{StorageProviderInfo, StorageProviderState},
};
Expand All @@ -71,30 +73,100 @@ pub mod pallet {
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>;

/// Peer ID is derived by hashing an encoded public key.
/// Usually represented in bytes.
/// 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<Self::AccountId>;

/// Market trait implementation for activating deals
type Market: Market<Self::AccountId, BlockNumberFor<Self>>;
/// Proving period for submitting Window PoSt, 24 hours is blocks

/// Window PoSt proving period — equivalent to 24 hours worth of blocks.
///
/// During the proving period, storage providers submit Spacetime proofs over smaller
/// intervals that make it unreasonable to cheat the system, if they fail to provide a proof
/// in time, they will get slashed.
///
/// In Filecoin, this concept starts with wall time — i.e. 24 hours — and is quantized into
/// discrete blocks. In our case, we need to consistently put out blocks, every 12 seconds
/// or 5 blocks per minute, as such, we instead work by block numbers only.
///
/// For example, consider that the first proving period was started at block `0`, to figure
/// out the proving period for an arbitrary block we must perform integer division between
/// the block number and the amount of blocks expected to be produced in 24 hours:
///
/// ```text
/// proving_period = current_block // DAYS
/// ```
///
/// If we produce 5 blocks per minute, in an hour, we produce `60 * 5 = 300`, following that
/// we produce `24 * 300 = 7200` blocks per day.
///
/// Hence, if we're in the block number `6873` we get `6873 // 7200 = 0` meaning we are in
/// the proving period `0`; moving that forward, consider the block `745711`, we'll get
/// `745711 // 7200 = 103`, thus, we're in the proving period `103`.
///
/// References:
/// * <https://spec.filecoin.io/#section-algorithms.pos.post.design>
/// * <https://spec.filecoin.io/#section-systems.filecoin_mining.storage_mining.proof-of-spacetime>
#[pallet::constant]
type WPoStProvingPeriod: Get<BlockNumberFor<Self>>;

/// Window PoSt challenge window — equivalent to 30 minutes worth of blocks.
///
/// To better understand the following explanation, read [`WPoStProvingPeriod`] first.
///
/// During the Window PoSt proving period, challenges are issued to storage providers to
/// prove they are still (correctly) storing the data they accepted, in the case of failure
/// the storage provider will get slashed and have the sector marked as faulty.
///
/// Given that our system works around block numbers, we have time quantization by default,
/// however it still is necessary to figure out where we stand in the current challenge
/// window.
///
/// Since we know that, in Filecoin, each 24 hour period is subdivided into 30 minute
/// epochs, we also subdivide our 24 hour period by 48, just in blocks.
///
/// Consider the block number `745711` (like in the [`WPoStProvingPeriod`]) and that every
/// 30 minutes, we produce `150` blocks (`300 blocks / hour // 2`). To calculate the current
/// challenge window we perform the following steps:
///
/// 1. calculate the current proving period — `745711 // 7200 = 103`
/// 2. calculate the start of said proving period — `103 * 7200 = 741600`
/// 3. calculate how many blocks elapsed since the beginning of said proving period —
/// `745711 - 741600 = 4111`
/// 4. calculate the number of elapsed challenge windows — `4111 // 150 = 27`
///
/// In some cases, it will be helpful to calculate the next deadline as well, picking up
/// where we left, we perform the following steps:
///
/// 5. calculate the block in which the current challenge window started —
/// for the "sub-block" `27 * 150 = 4050` & for the block `103 * 7200 + 4050 = 745650`
/// 6. calculate the next deadline — `745650 + 150 = 745800`
///
/// References:
/// * <https://spec.filecoin.io/#section-algorithms.pos.post.design>
/// Window PoSt challenge window (default 30 minutes in blocks)
#[pallet::constant]
type WPoStChallengeWindow: Get<BlockNumberFor<Self>>;

/// Minimum number of blocks past the current block a sector may be set to expire.
#[pallet::constant]
type MinSectorExpiration: Get<BlockNumberFor<Self>>;

/// Maximum number of blocks past the current block a sector may be set to expire.
#[pallet::constant]
type MaxSectorExpirationExtension: Get<BlockNumberFor<Self>>;

/// Maximum number of blocks a sector can stay in pre-committed state
#[pallet::constant]
type SectorMaximumLifetime: Get<BlockNumberFor<Self>>;

/// Maximum duration to allow for the sealing process for seal algorithms.
#[pallet::constant]
type MaxProveCommitDuration: Get<BlockNumberFor<Self>>;
Expand Down Expand Up @@ -148,8 +220,6 @@ pub mod pallet {
InvalidProofType,
/// Emitted when there is not enough funds to run an extrinsic.
NotEnoughFunds,
/// Emitted when a storage provider tries to commit more sectors than MAX_SECTORS.
MaxPreCommittedSectorExceeded,
/// Emitted when a sector fails to activate.
SectorActivateFailed,
/// Emitted when removing a pre_committed sector after proving fails.
Expand All @@ -168,11 +238,17 @@ pub mod pallet {
InvalidCid,
/// Emitted when a sector fails to activate
CouldNotActivateSector,
/// Emitted when a prove commit is sent after the dealine
/// Emitted when a prove commit is sent after the deadline
/// These precommits will be cleaned up in the hook
ProveCommitAfterDeadline,
/// Emitted when a PoSt supplied by by the SP is invalid
PoStProofInvalid,
/// Wrapper around the [`DeadlineError`] type.
DeadlineError(crate::deadline::DeadlineError),
/// Wrapper around the [`PartitionError`] type.
PartitionError(crate::partition::PartitionError),
/// Wrapper around the [`StorageProviderError`] type.
StorageProviderError(crate::storage_provider::StorageProviderError),
}

#[pallet::call]
Expand Down Expand Up @@ -224,7 +300,7 @@ pub mod pallet {
let sector_number = sector.sector_number;
let current_block = <frame_system::Pallet<T>>::block_number();
ensure!(
sector_number <= SECTORS_MAX.into(),
sector_number <= MAX_SECTORS.into(),
Error::<T>::InvalidSector
);
ensure!(
Expand Down Expand Up @@ -256,7 +332,7 @@ pub mod pallet {
deposit,
<frame_system::Pallet<T>>::block_number(),
))
.map_err(|_| Error::<T>::MaxPreCommittedSectorExceeded)?;
.map_err(|e| Error::<T>::StorageProviderError(e))?;
Ok(())
})?;
Self::deposit_event(Event::SectorPreCommitted { owner, sector });
Expand All @@ -275,12 +351,12 @@ pub mod pallet {
.map_err(|_| Error::<T>::StorageProviderNotFound)?;
let sector_number = sector.sector_number;
ensure!(
sector_number <= SECTORS_MAX.into(),
sector_number <= MAX_SECTORS.into(),
Error::<T>::InvalidSector
);
let precommit = sp
.get_pre_committed_sector(sector_number)
.map_err(|_| Error::<T>::InvalidSector)?;
.map_err(|e| Error::<T>::StorageProviderError(e))?;
let current_block = <frame_system::Pallet<T>>::block_number();
let prove_commit_due =
precommit.pre_commit_block_number + T::MaxProveCommitDuration::get();
Expand All @@ -299,9 +375,9 @@ pub mod pallet {
.as_mut()
.ok_or(Error::<T>::StorageProviderNotFound)?;
sp.activate_sector(sector_number, new_sector)
.map_err(|_| Error::<T>::SectorActivateFailed)?;
.map_err(|e| Error::<T>::StorageProviderError(e))?;
sp.remove_pre_committed_sector(sector_number)
.map_err(|_| Error::<T>::CouldNotRemoveSector)?;
.map_err(|e| Error::<T>::StorageProviderError(e))?;
Ok(())
})?;
let mut sector_deals = BoundedVec::new();
Expand Down Expand Up @@ -337,7 +413,11 @@ pub mod pallet {
log::error!(target: LOG_TARGET, "submit_window_post: PoSt submission is invalid {e:?}");
return Err(e.into());
}
// Set new deadline for PoSt submission
// Get deadlines and check the given deadline index is within range.
let deadlines = sp.get_deadlines();
let _deadline = deadlines
.load_deadline(windowed_post.deadline as usize)
.map_err(|e| Error::<T>::DeadlineError(e))?;
Self::deposit_event(Event::ValidPoStSubmitted { owner });
Ok(())
}
Expand Down
Loading

0 comments on commit f79e4f5

Please sign in to comment.