From d46136ba80bec407164e386460d17543fe6c37eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Duarte?= Date: Wed, 27 Nov 2024 05:37:19 +0000 Subject: [PATCH] fix(storage-provider-server): add deal validations & market account check (#614) --- .gitignore | 10 +++ cli/polka-storage-provider/server/src/main.rs | 29 ++++++-- cli/polka-storage-provider/server/src/rpc.rs | 73 ++++++++++++++++--- storagext/lib/src/types/market.rs | 10 +++ 4 files changed, 106 insertions(+), 16 deletions(-) diff --git a/.gitignore b/.gitignore index 9ecc55239..6d5bd5dc3 100644 --- a/.gitignore +++ b/.gitignore @@ -28,3 +28,13 @@ target/ # code coverage coverage/ + +# PoRep Params +*.porep.params +*.porep.vk +*.porep.vk.scale + +# PoSt Params +*.post.params +*.post.vk +*.post.vk.scale diff --git a/cli/polka-storage-provider/server/src/main.rs b/cli/polka-storage-provider/server/src/main.rs index b7d4c44a9..5ba405972 100644 --- a/cli/polka-storage-provider/server/src/main.rs +++ b/cli/polka-storage-provider/server/src/main.rs @@ -17,7 +17,7 @@ use primitives_proofs::{RegisteredPoStProof, RegisteredSealProof}; use rand::Rng; use storagext::{ multipair::{DebugPair, MultiPairSigner}, - StorageProviderClientExt, + MarketClientExt, StorageProviderClientExt, }; use subxt::{ ext::sp_core::{ @@ -113,6 +113,9 @@ pub enum ServerError { #[error("storage provider is not registered")] UnregisteredStorageProvider, + #[error("storage provider does not have a market account")] + NoMarketAccountStorageProvider, + #[error("registered proof does not match the configuration")] ProofMismatch, @@ -437,16 +440,28 @@ impl ServerConfiguration { ) -> Result { let xt_client = storagext::Client::new(rpc_address, RETRY_NUMBER, RETRY_INTERVAL).await?; + let storage_provider_account_id = subxt::utils::AccountId32( + // account_id() -> sp_core::crypto::AccountId + // as_ref() -> &[u8] + // * -> [u8] + *xt_keypair.account_id().as_ref(), + ); + // Check if the storage provider has been registered to the chain let storage_provider_info = xt_client - .retrieve_storage_provider(&subxt::utils::AccountId32( - // account_id() -> sp_core::crypto::AccountId - // as_ref() -> &[u8] - // * -> [u8] - *xt_keypair.account_id().as_ref(), - )) + .retrieve_storage_provider(&storage_provider_account_id) .await?; + // Check if the account exists on the market + xt_client + // Once subxt breaks our code with https://github.com/paritytech/subxt/pull/1850 + // we'll be able to make all this uniform + .retrieve_balance(subxt::ext::sp_runtime::AccountId32::new( + storage_provider_account_id.0, + )) + .await? + .ok_or(ServerError::NoMarketAccountStorageProvider)?; + match storage_provider_info { Some(storage_provider_info) => { if &storage_provider_info.info.window_post_proof_type != post_proof { diff --git a/cli/polka-storage-provider/server/src/rpc.rs b/cli/polka-storage-provider/server/src/rpc.rs index c44e2c400..4cd7873b3 100644 --- a/cli/polka-storage-provider/server/src/rpc.rs +++ b/cli/polka-storage-provider/server/src/rpc.rs @@ -5,11 +5,12 @@ use jsonrpsee::server::Server; use polka_storage_provider_common::rpc::{ CidString, RpcError, ServerInfo, StorageProviderRpcServer, }; -use primitives_commitment::Commitment; +use primitives_commitment::{CommP, Commitment, CommitmentKind}; use storagext::{ types::market::{ClientDealProposal as SxtClientDealProposal, DealProposal as SxtDealProposal}, MarketClientExt, }; +use subxt::tx::Signer; use tokio::sync::mpsc::UnboundedSender; use tokio_util::sync::CancellationToken; use tower_http::cors::{Any, CorsLayer}; @@ -42,21 +43,75 @@ impl StorageProviderRpcServer for RpcServerState { } async fn propose_deal(&self, deal: SxtDealProposal) -> Result { + // TODO(@jmg-duarte,26/11/2024): proper unit or e2e testing of these validations + if deal.piece_size > self.server_info.post_proof.sector_size().bytes() { - // once again, the rpc error is wrong, we'll need to fix that return Err(RpcError::invalid_params( "Piece size cannot be larger than the registered sector size", None, )); } - // TODO(@jmg-duarte,25/11/2024): Add sanity validations - // end_block > start_block - // available balance (sp & client) - // the provider matches us - // storage price per block > 0 - // piece size <= 2048 && power of two - // check the piece_cid code + if deal.start_block > deal.end_block { + return Err(RpcError::invalid_params( + "start_block cannot be after end_block", + None, + )); + } + + if deal.provider != self.xt_keypair.account_id() { + return Err(RpcError::invalid_params( + "deal's provider ID does not match the current provider ID", + None, + )); + } + + if deal.piece_cid.codec() != CommP::multicodec() { + return Err(RpcError::invalid_params( + "piece_cid is not a piece commitment", + None, + )); + } + + if !deal.piece_size.is_power_of_two() { + return Err(RpcError::invalid_params( + "invalid piece_size, must be a power of two", + None, + )); + } + + if deal.storage_price_per_block == 0 { + return Err(RpcError::invalid_params( + "storage_price_per_block must be greater than 0", + None, + )); + } + + let storage_provider_balance = self + .xt_client + .retrieve_balance(self.xt_keypair.account_id()) + .await? + .ok_or_else(|| RpcError::internal_error("Storage Provider not found", None))?; + + if storage_provider_balance.free < deal.provider_collateral { + return Err(RpcError::invalid_params( + "storage provider balance is lower than the deal's collateral", + None, + )); + } + + let client_balance = self + .xt_client + .retrieve_balance(deal.client.clone()) + .await? + .ok_or_else(|| RpcError::internal_error("Client not found", None))?; + + if client_balance.free < deal.cost() { + return Err(RpcError::invalid_params( + "client's balance is lower than the deal's cost", + None, + )); + } let cid = self .deal_db diff --git a/storagext/lib/src/types/market.rs b/storagext/lib/src/types/market.rs index 13ef5850f..49e3c6366 100644 --- a/storagext/lib/src/types/market.rs +++ b/storagext/lib/src/types/market.rs @@ -50,6 +50,16 @@ pub struct DealProposal { pub state: RuntimeDealState, } +impl DealProposal { + /// Calculates the deal cost for the client — + /// `(end_block - start_block) * storage_price_per_block`. + pub fn cost(&self) -> Currency { + let deal_duration = self.end_block - self.start_block; + // Cast is save because it's to a bigger int size + self.storage_price_per_block * (deal_duration as u128) + } +} + impl From for RuntimeDealProposal {