From 6eef4b00217a9c0493f274b09f756aa50bae964f Mon Sep 17 00:00:00 2001 From: Konrad Stepniak Date: Thu, 20 Jun 2024 16:06:47 +0200 Subject: [PATCH] feat(market-pallet): verify_deaals_for_activation --- pallets/market/Cargo.toml | 2 +- pallets/market/src/lib.rs | 199 +++++++++++++++++++++++++++++++++++++- 2 files changed, 197 insertions(+), 4 deletions(-) diff --git a/pallets/market/Cargo.toml b/pallets/market/Cargo.toml index 127130c83..e83ca3b50 100644 --- a/pallets/market/Cargo.toml +++ b/pallets/market/Cargo.toml @@ -19,6 +19,7 @@ targets = ["x86_64-unknown-linux-gnu"] cid = { workspace = true, default-features = false, features = ["scale-codec"] } codec = { workspace = true, default-features = false, features = ["derive"] } log = { workspace = true } +multihash-codetable = { workspace = true, features = ["blake2b"] } scale-info = { workspace = true, default-features = false, features = ["derive"] } # frame deps @@ -31,7 +32,6 @@ sp-std = { workspace = true, default-features = false } [dev-dependencies] blake2b_simd = { workspace = true } env_logger = { workspace = true } -multihash-codetable = { workspace = true, features = ["blake2b"] } pallet-balances = { workspace = true, default-features = false } sp-core = { workspace = true, default-features = false } sp-io = { workspace = true } diff --git a/pallets/market/src/lib.rs b/pallets/market/src/lib.rs index d4b6201b5..c501d5d72 100644 --- a/pallets/market/src/lib.rs +++ b/pallets/market/src/lib.rs @@ -36,9 +36,10 @@ pub mod pallet { ExistenceRequirement::{AllowDeath, KeepAlive}, ReservableCurrency, }, - PalletId, + PalletError, PalletId, }; - use frame_system::{pallet_prelude::*, Config as SystemConfig}; + use frame_system::{pallet_prelude::*, Config as SystemConfig, Pallet as System}; + use multihash_codetable::{Code, MultihashDigest}; use scale_info::TypeInfo; use sp_arithmetic::traits::BaseArithmetic; use sp_std::vec::Vec; @@ -126,7 +127,7 @@ pub mod pallet { /// Reference: pub struct ActiveDealState { /// Sector in which given piece has been included - sector_number: u128, + sector_number: SectorNumber, /// At which block (time) the deal's sector has been activated. sector_start_block: BlockNumber, @@ -201,6 +202,9 @@ pub mod pallet { } } + type DealProposalOf = + DealProposal<::AccountId, BalanceOf, BlockNumberFor>; + /// After Storage Client has successfully negotiated with the Storage Provider, they prepare a DealProposal, /// sign it with their signature and send to the Storage Provider. /// Storage Provider only after successful file transfer and verification of the data, calls an extrinsic `market.publish_storage_deals`. @@ -212,6 +216,47 @@ pub mod pallet { pub client_signature: OffchainSignature, } + // TODO(@th7nder,20/06/2024): this DOES NOT belong here. it should be somewhere else. + #[allow(non_camel_case_types)] + #[derive(Debug, Decode, Encode, TypeInfo, Eq, PartialEq, Clone)] + pub enum RegisteredSealProof { + StackedDRG2KiBV1P1, + } + + impl RegisteredSealProof { + pub fn sector_size(&self) -> SectorSize { + SectorSize::_2KiB + } + } + + /// 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, + } + + impl SectorSize { + /// + pub fn bytes(&self) -> u64 { + match self { + SectorSize::_2KiB => 2 << 10, + } + } + } + + // TODO(@th7nder,20/06/2024): this DOES not belong here. it should be somewhere else. + pub type SectorNumber = u64; + + #[derive(Debug, Decode, Encode, TypeInfo, Eq, PartialEq, Clone)] + pub struct SectorDeal { + pub sector_number: SectorNumber, + pub sector_expiry: BlockNumber, + pub sector_type: RegisteredSealProof, + pub deal_ids: BoundedVec>, + } + // verify_deals_for_activation is called by Storage Provider Pallllllet! + // it's not an extrinsic then? + #[pallet::pallet] pub struct Pallet(_); @@ -266,6 +311,18 @@ pub mod pallet { AllProposalsInvalid, /// `publish_storage_deals`'s core logic was invoked with a broken invariant that should be called by `validate_deals`. UnexpectedValidationError, + DuplicateDeal(DealId), + DealPreconditionFailed(DealId), + DealNotFound(DealId), + DealActivationError(DealId, DealActivationError), + DealsTooLargeToFitIntoSector(SectorNumber), + } + + #[derive(RuntimeDebug, Encode, Decode, TypeInfo, PalletError)] + pub enum DealActivationError { + InvalidProvider, + StartBlockElapsed, + SectorExpiresBeforeDeal, } // NOTE(@th7nder,18/06/2024): @@ -456,6 +513,142 @@ pub mod pallet { Ok(()) } + /// Exposed to the Storage Provider Pallet + /// They call it when doing `precommit`. + /// Verify that a given set of storage deals is valid for a sector currently being PreCommitted + /// and return UnsealedCID for the set of deals. + // TODO(@th7nder,20/06/2024): what's a good number for BoundedVec `sector_deals`? + pub fn verify_deals_for_activation( + storage_provider: &T::AccountId, + sector_deals: BoundedVec>, ConstU32<32>>, + ) -> Result, ConstU32<32>>, DispatchError> { + // TODO: + // - unit tests + // - trait in primitives + // - docs + let curr_block = System::::block_number(); + let mut unsealed_cids = BoundedVec::new(); + for sector in sector_deals { + let proposals = Self::proposals_for_deals(sector.deal_ids)?; + let sector_size = sector.sector_type.sector_size(); + Self::validate_deals_for_sector( + &proposals, + storage_provider, + sector.sector_number, + sector.sector_expiry, + curr_block, + sector_size, + )?; + + // Sealing a Sector without Deals, Committed Capacity Only. + let commd = if proposals.is_empty() { + None + } else { + Some(Self::compute_commd( + proposals.iter().map(|(_, deal)| deal), + sector.sector_type, + )?) + }; + + // PRE-COND: can't fail, unsealed_cids<_, X> == BoundedVec<_ X> == sector_deals<_, X> + unsealed_cids + .try_push(commd) + .map_err(|_| "programmer error, there should be space for Cids")?; + } + + Ok(unsealed_cids) + } + + /// + fn compute_commd<'a>( + _proposals: impl IntoIterator>, + _sector_type: RegisteredSealProof, + ) -> Result { + // TODO(@th7nder,#92,21/06/2024): + // https://github.com/filecoin-project/rust-fil-proofs/blob/daec42b64ae6bf9a537545d5f116d57b9a29cc11/filecoin-proofs/src/pieces.rs#L85 + let cid = Cid::new_v1( + CID_CODEC, + Code::Blake2b256.digest(b"placeholder-to-be-done"), + ); + + Ok(cid) + } + + /// + fn validate_deals_for_sector( + deals: &BoundedVec<(DealId, DealProposalOf), ConstU32<32>>, + provider: &T::AccountId, + sector_number: SectorNumber, + sector_expiry: BlockNumberFor, + sector_activation: BlockNumberFor, + sector_size: SectorSize, + ) -> DispatchResult { + let mut total_deal_space = 0; + for (deal_id, deal) in deals { + Self::validate_deal_can_activate(deal, provider, sector_expiry, sector_activation) + .map_err(|e| Error::::DealActivationError(*deal_id, e))?; + total_deal_space += deal.piece_size; + } + + ensure!( + total_deal_space <= sector_size.bytes(), + Error::::DealsTooLargeToFitIntoSector(sector_number) + ); + + Ok(()) + } + + /// + fn validate_deal_can_activate( + deal: &DealProposalOf, + provider: &T::AccountId, + sector_expiry: BlockNumberFor, + sector_activation: BlockNumberFor, + ) -> Result<(), DealActivationError> { + ensure!( + *provider == deal.provider, + DealActivationError::InvalidProvider + ); + ensure!( + sector_activation <= deal.start_block, + DealActivationError::StartBlockElapsed + ); + ensure!( + sector_expiry >= deal.end_block, + DealActivationError::SectorExpiresBeforeDeal + ); + + Ok(()) + } + + fn proposals_for_deals( + deal_ids: BoundedVec>, + ) -> Result), ConstU32<32>>, DispatchError> { + let mut unique_deals: BoundedBTreeSet> = BoundedBTreeSet::new(); + let mut proposals = BoundedVec::new(); + for deal_id in deal_ids { + ensure!( + !unique_deals.contains(&deal_id), + Error::::DuplicateDeal(deal_id) + ); + + // PRE-COND: always succeeds, unique_deals has the same boundary as sector.deal_ids[] + unique_deals + .try_insert(deal_id) + .map_err(|deal_id| Error::::DealPreconditionFailed(deal_id))?; + + let proposal: DealProposalOf = Proposals::::try_get(&deal_id) + .map_err(|_| Error::::DealNotFound(deal_id))?; + + // PRE-COND: always succeeds, unique_deals has the same boundary as sector.deal_ids[] + proposals + .try_push((deal_id, proposal)) + .map_err(|_| Error::::DealPreconditionFailed(deal_id))?; + } + + Ok(proposals) + } + fn generate_deal_id() -> DealId { let ret = NextDealId::::get(); let next = ret