Skip to content

Commit f79e4f5

Browse files
committed
WIP: Add deadline and partition data types
1 parent ee342d2 commit f79e4f5

File tree

7 files changed

+474
-21
lines changed

7 files changed

+474
-21
lines changed
+174
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
use codec::{Decode, Encode};
2+
use frame_support::{
3+
pallet_prelude::*,
4+
sp_runtime::{BoundedBTreeMap, BoundedVec},
5+
PalletError,
6+
};
7+
use scale_info::TypeInfo;
8+
9+
use crate::partition::{Partition, PartitionNumber, MAX_PARTITIONS};
10+
11+
type DeadlineResult<T> = Result<T, DeadlineError>;
12+
13+
/// Deadline holds the state for all sectors due at a specific deadline.
14+
///
15+
/// A deadline exists along side 47 other deadlines (1 for every 30 minutes in a day).
16+
/// Only one deadline may be active for a given proving window.
17+
#[derive(Clone, Debug, Decode, Encode, PartialEq, TypeInfo)]
18+
pub struct Deadline<BlockNumber> {
19+
/// Partitions in this deadline. Indexed on partition number.
20+
pub partitions:
21+
BoundedBTreeMap<PartitionNumber, Partition<BlockNumber>, ConstU32<MAX_PARTITIONS>>,
22+
23+
/// Partition numbers with sectors that terminated early.
24+
pub early_terminations: BoundedBTreeSet<PartitionNumber, ConstU32<MAX_PARTITIONS>>,
25+
26+
/// The number of non-terminated sectors in this deadline (incl faulty).
27+
pub live_sectors: u64,
28+
29+
/// The total number of sectors in this deadline (incl dead).
30+
pub total_sectors: u64,
31+
}
32+
33+
impl<BlockNumber> Deadline<BlockNumber> {
34+
pub fn new() -> Self {
35+
Self {
36+
partitions: BoundedBTreeMap::new(),
37+
early_terminations: BoundedBTreeSet::new(),
38+
live_sectors: 0,
39+
total_sectors: 0,
40+
}
41+
}
42+
43+
pub fn update_deadline(&mut self, new_dl: Self) {
44+
self.early_terminations = new_dl.early_terminations;
45+
self.live_sectors = new_dl.live_sectors;
46+
self.total_sectors = new_dl.total_sectors;
47+
self.partitions = new_dl.partitions;
48+
}
49+
50+
/// Processes a series of PoSts, recording proven partitions and marking skipped
51+
/// sectors as faulty.
52+
pub fn record_proven_sectors(&mut self) {
53+
todo!()
54+
}
55+
}
56+
57+
#[derive(Clone, Debug, Decode, Encode, PartialEq, TypeInfo)]
58+
pub struct Deadlines<BlockNumber> {
59+
/// Deadlines indexed by their proving periods — e.g. for proving period 7, find it in
60+
/// `deadlines[7]` — proving periods are present in the interval `[0, 47]`.
61+
///
62+
/// Bounded to 48 elements since that's the set amount of deadlines per proving period.
63+
///
64+
/// In the original implementation, the information is kept in a separated structure, possibly
65+
/// to make fetching the state more efficient as this is kept in the storage providers
66+
/// blockstore. However, we're keeping all the state on-chain
67+
///
68+
/// References:
69+
/// * <https://github.com/filecoin-project/builtin-actors/blob/17ede2b256bc819dc309edf38e031e246a516486/actors/miner/src/state.rs#L105-L108>
70+
/// * <https://spec.filecoin.io/#section-algorithms.pos.post.constants--terminology>
71+
/// * <https://spec.filecoin.io/#section-algorithms.pos.post.design>
72+
pub due: BoundedVec<Deadline<BlockNumber>, ConstU32<48>>,
73+
}
74+
75+
impl<BlockNumber> Deadlines<BlockNumber> {
76+
/// Constructor function.
77+
pub fn new() -> Self {
78+
Self {
79+
due: BoundedVec::new(),
80+
}
81+
}
82+
83+
/// Get the amount of deadlines that are due.
84+
pub fn len(&self) -> usize {
85+
self.due.len()
86+
}
87+
88+
/// Inserts a new deadline.
89+
/// Fails if the deadline insertion fails.
90+
/// Returns the deadline index it inserted the deadline at
91+
///
92+
/// I am not sure if this should just insert the new deadline at the back and return the index
93+
/// or take in the index and insert the deadline in there.
94+
pub fn insert_deadline(
95+
&mut self,
96+
new_deadline: Deadline<BlockNumber>,
97+
) -> DeadlineResult<usize> {
98+
self.due
99+
.try_push(new_deadline)
100+
.map_err(|_| DeadlineError::CouldNotInsertDeadline)?;
101+
// No underflow if the above was successful, minimum length 1
102+
Ok(self.due.len() - 1)
103+
}
104+
105+
/// Loads a deadline from the given index.
106+
/// Fails if the index does not exist or is out of range.
107+
pub fn load_deadline(&self, idx: usize) -> DeadlineResult<&Deadline<BlockNumber>> {
108+
// Ensure the provided index is within range.
109+
ensure!(self.len() > idx, DeadlineError::DeadlineIndexOutOfRange);
110+
self.due.get(idx).ok_or(DeadlineError::DeadlineNotFound)
111+
}
112+
113+
/// Updates a deadline at the given index.
114+
pub fn update_deadline(
115+
&mut self,
116+
idx: usize,
117+
new_deadline: Deadline<BlockNumber>,
118+
) -> DeadlineResult<()> {
119+
let dl = self
120+
.due
121+
.get_mut(idx)
122+
.ok_or(DeadlineError::DeadlineNotFound)?;
123+
dl.update_deadline(new_deadline);
124+
125+
Ok(())
126+
}
127+
}
128+
129+
#[derive(Clone, Debug, Decode, Encode, PartialEq, TypeInfo)]
130+
pub struct DeadlineInfo<BlockNumber> {
131+
/// The block number at which this info was calculated.
132+
pub block_number: BlockNumber,
133+
134+
/// The block number at which the proving period for this deadline starts.
135+
pub period_start: BlockNumber,
136+
137+
/// The deadline index within its proving window.
138+
pub deadline_idx: u32,
139+
140+
/// The first block number from which a proof can be submitted.
141+
pub open: BlockNumber,
142+
143+
/// The first block number from which a proof can *no longer* be submitted.
144+
pub close: BlockNumber,
145+
}
146+
147+
#[derive(Decode, Encode, PalletError, TypeInfo, RuntimeDebug)]
148+
pub enum DeadlineError {
149+
/// Emitted when the passed in deadline index supplied for `submit_windowed_post` is out of range.
150+
DeadlineIndexOutOfRange,
151+
/// Emitted when a trying to get a deadline index but fails because that index does not exist.
152+
DeadlineNotFound,
153+
/// Emitted when a given index in `Deadlines` already exists and try to insert a deadline on that index.
154+
DeadlineIndexExists,
155+
/// Emitted when trying to insert a new deadline fails.
156+
CouldNotInsertDeadline,
157+
}
158+
159+
#[cfg(test)]
160+
mod test {
161+
use super::*;
162+
163+
#[test]
164+
fn update_and_load_deadline() -> DeadlineResult<()> {
165+
let mut dls: Deadlines<u32> = Deadlines::new();
166+
let dl: Deadline<u32> = Deadline::new();
167+
168+
let idx = dls.insert_deadline(dl.clone())?;
169+
dls.update_deadline(idx, dl.clone())?;
170+
let loaded_dl = dls.load_deadline(idx)?;
171+
assert_eq!(&dl, loaded_dl);
172+
Ok(())
173+
}
174+
}

pallets/storage-provider/src/lib.rs

+92-12
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ mod mock;
2121
#[cfg(test)]
2222
mod test;
2323

24+
mod deadline;
25+
mod partition;
2426
mod proofs;
2527
mod sector;
2628
mod storage_provider;
@@ -53,7 +55,7 @@ pub mod pallet {
5355
},
5456
sector::{
5557
ProveCommitSector, SectorOnChainInfo, SectorPreCommitInfo, SectorPreCommitOnChainInfo,
56-
SECTORS_MAX,
58+
MAX_SECTORS,
5759
},
5860
storage_provider::{StorageProviderInfo, StorageProviderState},
5961
};
@@ -71,30 +73,100 @@ pub mod pallet {
7173
pub trait Config: frame_system::Config {
7274
/// Because this pallet emits events, it depends on the runtime's definition of an event.
7375
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
76+
7477
/// Peer ID is derived by hashing an encoded public key.
7578
/// Usually represented in bytes.
7679
/// https://github.com/libp2p/specs/blob/2ea41e8c769f1bead8e637a9d4ebf8c791976e8a/peer-ids/peer-ids.md#peer-ids
7780
/// More information about libp2p peer ids: https://docs.libp2p.io/concepts/fundamentals/peers/
7881
type PeerId: Clone + Debug + Decode + Encode + Eq + TypeInfo;
82+
7983
/// Currency mechanism, used for collateral
8084
type Currency: ReservableCurrency<Self::AccountId>;
85+
8186
/// Market trait implementation for activating deals
8287
type Market: Market<Self::AccountId, BlockNumberFor<Self>>;
83-
/// Proving period for submitting Window PoSt, 24 hours is blocks
88+
89+
/// Window PoSt proving period — equivalent to 24 hours worth of blocks.
90+
///
91+
/// During the proving period, storage providers submit Spacetime proofs over smaller
92+
/// intervals that make it unreasonable to cheat the system, if they fail to provide a proof
93+
/// in time, they will get slashed.
94+
///
95+
/// In Filecoin, this concept starts with wall time — i.e. 24 hours — and is quantized into
96+
/// discrete blocks. In our case, we need to consistently put out blocks, every 12 seconds
97+
/// or 5 blocks per minute, as such, we instead work by block numbers only.
98+
///
99+
/// For example, consider that the first proving period was started at block `0`, to figure
100+
/// out the proving period for an arbitrary block we must perform integer division between
101+
/// the block number and the amount of blocks expected to be produced in 24 hours:
102+
///
103+
/// ```text
104+
/// proving_period = current_block // DAYS
105+
/// ```
106+
///
107+
/// If we produce 5 blocks per minute, in an hour, we produce `60 * 5 = 300`, following that
108+
/// we produce `24 * 300 = 7200` blocks per day.
109+
///
110+
/// Hence, if we're in the block number `6873` we get `6873 // 7200 = 0` meaning we are in
111+
/// the proving period `0`; moving that forward, consider the block `745711`, we'll get
112+
/// `745711 // 7200 = 103`, thus, we're in the proving period `103`.
113+
///
114+
/// References:
115+
/// * <https://spec.filecoin.io/#section-algorithms.pos.post.design>
116+
/// * <https://spec.filecoin.io/#section-systems.filecoin_mining.storage_mining.proof-of-spacetime>
84117
#[pallet::constant]
85118
type WPoStProvingPeriod: Get<BlockNumberFor<Self>>;
119+
120+
/// Window PoSt challenge window — equivalent to 30 minutes worth of blocks.
121+
///
122+
/// To better understand the following explanation, read [`WPoStProvingPeriod`] first.
123+
///
124+
/// During the Window PoSt proving period, challenges are issued to storage providers to
125+
/// prove they are still (correctly) storing the data they accepted, in the case of failure
126+
/// the storage provider will get slashed and have the sector marked as faulty.
127+
///
128+
/// Given that our system works around block numbers, we have time quantization by default,
129+
/// however it still is necessary to figure out where we stand in the current challenge
130+
/// window.
131+
///
132+
/// Since we know that, in Filecoin, each 24 hour period is subdivided into 30 minute
133+
/// epochs, we also subdivide our 24 hour period by 48, just in blocks.
134+
///
135+
/// Consider the block number `745711` (like in the [`WPoStProvingPeriod`]) and that every
136+
/// 30 minutes, we produce `150` blocks (`300 blocks / hour // 2`). To calculate the current
137+
/// challenge window we perform the following steps:
138+
///
139+
/// 1. calculate the current proving period — `745711 // 7200 = 103`
140+
/// 2. calculate the start of said proving period — `103 * 7200 = 741600`
141+
/// 3. calculate how many blocks elapsed since the beginning of said proving period —
142+
/// `745711 - 741600 = 4111`
143+
/// 4. calculate the number of elapsed challenge windows — `4111 // 150 = 27`
144+
///
145+
/// In some cases, it will be helpful to calculate the next deadline as well, picking up
146+
/// where we left, we perform the following steps:
147+
///
148+
/// 5. calculate the block in which the current challenge window started —
149+
/// for the "sub-block" `27 * 150 = 4050` & for the block `103 * 7200 + 4050 = 745650`
150+
/// 6. calculate the next deadline — `745650 + 150 = 745800`
151+
///
152+
/// References:
153+
/// * <https://spec.filecoin.io/#section-algorithms.pos.post.design>
86154
/// Window PoSt challenge window (default 30 minutes in blocks)
87155
#[pallet::constant]
88156
type WPoStChallengeWindow: Get<BlockNumberFor<Self>>;
157+
89158
/// Minimum number of blocks past the current block a sector may be set to expire.
90159
#[pallet::constant]
91160
type MinSectorExpiration: Get<BlockNumberFor<Self>>;
161+
92162
/// Maximum number of blocks past the current block a sector may be set to expire.
93163
#[pallet::constant]
94164
type MaxSectorExpirationExtension: Get<BlockNumberFor<Self>>;
165+
95166
/// Maximum number of blocks a sector can stay in pre-committed state
96167
#[pallet::constant]
97168
type SectorMaximumLifetime: Get<BlockNumberFor<Self>>;
169+
98170
/// Maximum duration to allow for the sealing process for seal algorithms.
99171
#[pallet::constant]
100172
type MaxProveCommitDuration: Get<BlockNumberFor<Self>>;
@@ -148,8 +220,6 @@ pub mod pallet {
148220
InvalidProofType,
149221
/// Emitted when there is not enough funds to run an extrinsic.
150222
NotEnoughFunds,
151-
/// Emitted when a storage provider tries to commit more sectors than MAX_SECTORS.
152-
MaxPreCommittedSectorExceeded,
153223
/// Emitted when a sector fails to activate.
154224
SectorActivateFailed,
155225
/// Emitted when removing a pre_committed sector after proving fails.
@@ -168,11 +238,17 @@ pub mod pallet {
168238
InvalidCid,
169239
/// Emitted when a sector fails to activate
170240
CouldNotActivateSector,
171-
/// Emitted when a prove commit is sent after the dealine
241+
/// Emitted when a prove commit is sent after the deadline
172242
/// These precommits will be cleaned up in the hook
173243
ProveCommitAfterDeadline,
174244
/// Emitted when a PoSt supplied by by the SP is invalid
175245
PoStProofInvalid,
246+
/// Wrapper around the [`DeadlineError`] type.
247+
DeadlineError(crate::deadline::DeadlineError),
248+
/// Wrapper around the [`PartitionError`] type.
249+
PartitionError(crate::partition::PartitionError),
250+
/// Wrapper around the [`StorageProviderError`] type.
251+
StorageProviderError(crate::storage_provider::StorageProviderError),
176252
}
177253

178254
#[pallet::call]
@@ -224,7 +300,7 @@ pub mod pallet {
224300
let sector_number = sector.sector_number;
225301
let current_block = <frame_system::Pallet<T>>::block_number();
226302
ensure!(
227-
sector_number <= SECTORS_MAX.into(),
303+
sector_number <= MAX_SECTORS.into(),
228304
Error::<T>::InvalidSector
229305
);
230306
ensure!(
@@ -256,7 +332,7 @@ pub mod pallet {
256332
deposit,
257333
<frame_system::Pallet<T>>::block_number(),
258334
))
259-
.map_err(|_| Error::<T>::MaxPreCommittedSectorExceeded)?;
335+
.map_err(|e| Error::<T>::StorageProviderError(e))?;
260336
Ok(())
261337
})?;
262338
Self::deposit_event(Event::SectorPreCommitted { owner, sector });
@@ -275,12 +351,12 @@ pub mod pallet {
275351
.map_err(|_| Error::<T>::StorageProviderNotFound)?;
276352
let sector_number = sector.sector_number;
277353
ensure!(
278-
sector_number <= SECTORS_MAX.into(),
354+
sector_number <= MAX_SECTORS.into(),
279355
Error::<T>::InvalidSector
280356
);
281357
let precommit = sp
282358
.get_pre_committed_sector(sector_number)
283-
.map_err(|_| Error::<T>::InvalidSector)?;
359+
.map_err(|e| Error::<T>::StorageProviderError(e))?;
284360
let current_block = <frame_system::Pallet<T>>::block_number();
285361
let prove_commit_due =
286362
precommit.pre_commit_block_number + T::MaxProveCommitDuration::get();
@@ -299,9 +375,9 @@ pub mod pallet {
299375
.as_mut()
300376
.ok_or(Error::<T>::StorageProviderNotFound)?;
301377
sp.activate_sector(sector_number, new_sector)
302-
.map_err(|_| Error::<T>::SectorActivateFailed)?;
378+
.map_err(|e| Error::<T>::StorageProviderError(e))?;
303379
sp.remove_pre_committed_sector(sector_number)
304-
.map_err(|_| Error::<T>::CouldNotRemoveSector)?;
380+
.map_err(|e| Error::<T>::StorageProviderError(e))?;
305381
Ok(())
306382
})?;
307383
let mut sector_deals = BoundedVec::new();
@@ -337,7 +413,11 @@ pub mod pallet {
337413
log::error!(target: LOG_TARGET, "submit_window_post: PoSt submission is invalid {e:?}");
338414
return Err(e.into());
339415
}
340-
// Set new deadline for PoSt submission
416+
// Get deadlines and check the given deadline index is within range.
417+
let deadlines = sp.get_deadlines();
418+
let _deadline = deadlines
419+
.load_deadline(windowed_post.deadline as usize)
420+
.map_err(|e| Error::<T>::DeadlineError(e))?;
341421
Self::deposit_event(Event::ValidPoStSubmitted { owner });
342422
Ok(())
343423
}

0 commit comments

Comments
 (0)