diff --git a/Cargo.lock b/Cargo.lock index 2e98a581a..6454c6e93 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7843,7 +7843,9 @@ dependencies = [ "parity-scale-codec", "scale-info", "sp-api 34.0.0", + "sp-core 34.0.0", "sp-runtime 39.0.5", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?branch=stable2409)", ] [[package]] diff --git a/client/blockchain-service/src/commands.rs b/client/blockchain-service/src/commands.rs index c190eb7a5..a7e8c1a18 100644 --- a/client/blockchain-service/src/commands.rs +++ b/client/blockchain-service/src/commands.rs @@ -64,6 +64,10 @@ pub enum BlockchainServiceCommand { block_number: BlockNumber, callback: tokio::sync::oneshot::Sender>, }, + WaitForNumBlocks { + number_of_blocks: BlockNumber, + callback: tokio::sync::oneshot::Sender>, + }, WaitForTick { tick_number: TickNumber, callback: @@ -239,6 +243,9 @@ pub trait BlockchainServiceInterface { /// Wait for a block number. async fn wait_for_block(&self, block_number: BlockNumber) -> Result<()>; + /// Wait for a number blocks to pass. + async fn wait_for_num_blocks(&self, number_of_blocks: BlockNumber) -> Result<()>; + /// Wait for a tick number. async fn wait_for_tick(&self, tick_number: TickNumber) -> Result<(), ApiError>; @@ -487,6 +494,19 @@ where Ok(()) } + async fn wait_for_num_blocks(&self, number_of_blocks: BlockNumber) -> Result<()> { + let (callback, rx) = tokio::sync::oneshot::channel(); + // Build command to send to blockchain service. + let message = BlockchainServiceCommand::WaitForNumBlocks { + number_of_blocks, + callback, + }; + self.send(message).await; + let rx = rx.await.expect("Failed to receive response from BlockchainService. Probably means BlockchainService has crashed."); + rx.await.expect("Failed to wait for block"); + Ok(()) + } + async fn wait_for_tick(&self, tick_number: TickNumber) -> Result<(), ApiError> { let (callback, rx) = tokio::sync::oneshot::channel(); // Build command to send to blockchain service. diff --git a/client/blockchain-service/src/handler.rs b/client/blockchain-service/src/handler.rs index bc5a48796..3d33f81df 100644 --- a/client/blockchain-service/src/handler.rs +++ b/client/blockchain-service/src/handler.rs @@ -31,7 +31,10 @@ use pallet_storage_providers_runtime_api::{ use shc_actors_framework::actor::{Actor, ActorEventLoop}; use shc_common::{ blockchain_utils::{convert_raw_multiaddresses_to_multiaddr, get_events_at_block}, - types::{BlockNumber, ParachainClient, TickNumber}, + types::{ + BlockNumber, EitherBucketOrBspId, Fingerprint, ParachainClient, StorageProviderId, + StorageRequestMetadata, TickNumber, BCSV_KEY_TYPE, + }, }; use crate::{ @@ -352,6 +355,28 @@ where } } } + BlockchainServiceCommand::WaitForNumBlocks { + number_of_blocks, + callback, + } => { + let current_block_number = self.client.info().best_number; + + let (tx, rx) = tokio::sync::oneshot::channel(); + + self.wait_for_block_request_by_number + .entry(current_block_number + number_of_blocks) + .or_insert_with(Vec::new) + .push(tx); + + match callback.send(rx) { + Ok(_) => { + trace!(target: LOG_TARGET, "Block message receiver sent successfully"); + } + Err(e) => { + error!(target: LOG_TARGET, "Failed to send block message receiver: {:?}", e); + } + } + } BlockchainServiceCommand::WaitForTick { tick_number, callback, @@ -1268,7 +1293,44 @@ where Some(ManagedProvider::Bsp(_)) => { self.bsp_initial_sync(); } - Some(ManagedProvider::Msp(_)) => { + Some(StorageProviderId::MainStorageProvider(msp_id)) => { + // TODO: Send events to check that this node has a Forest Storage for each Bucket this MSP manages. + // TODO: Catch up to Forest root writes in the Bucket's Forests. + + info!(target: LOG_TARGET, "Checking for storage requests for this MSP"); + + let storage_requests: BTreeMap = match self + .client + .runtime_api() + .pending_storage_requests_by_msp(block_hash, msp_id) + { + Ok(sr) => sr, + Err(_) => { + // If querying for pending storage requests fail, do not try to answer them + warn!(target: LOG_TARGET, "Failed to get pending storage request"); + return; + } + }; + + info!( + "We have {} pending storage requests", + storage_requests.len() + ); + + // loop over each pending storage requests to start a new storage request task for the MSP + for (file_key, sr) in storage_requests { + self.emit(NewStorageRequest { + who: sr.owner, + file_key: file_key.into(), + bucket_id: sr.bucket_id, + location: sr.location, + fingerprint: Fingerprint::from(sr.fingerprint.as_bytes()), + size: sr.size, + user_peer_ids: sr.user_peer_ids, + expires_at: sr.expires_at, + }) + } + self.msp_initial_sync(); } None => { diff --git a/client/common/src/types.rs b/client/common/src/types.rs index f3cc5edd2..a30f1a709 100644 --- a/client/common/src/types.rs +++ b/client/common/src/types.rs @@ -76,6 +76,7 @@ pub type Balance = pallet_storage_providers::types::BalanceOf; pub type OpaqueBlock = storage_hub_runtime::opaque::Block; pub type BlockHash = ::Hash; pub type PeerId = pallet_file_system::types::PeerId; +pub type StorageRequestMetadata = pallet_file_system::types::StorageRequestMetadata; pub type MaxBatchConfirmStorageRequests = ::MaxBatchConfirmStorageRequests; diff --git a/client/rpc/src/lib.rs b/client/rpc/src/lib.rs index e48ee557b..4c4be3731 100644 --- a/client/rpc/src/lib.rs +++ b/client/rpc/src/lib.rs @@ -22,15 +22,7 @@ use tokio::{fs, fs::create_dir_all, sync::RwLock}; use pallet_file_system_runtime_api::FileSystemApi as FileSystemRuntimeApi; use pallet_proofs_dealer_runtime_api::ProofsDealerApi as ProofsDealerRuntimeApi; -use shc_common::{ - consts::CURRENT_FOREST_KEY, - types::{ - BackupStorageProviderId, BlockNumber, BucketId, ChunkId, CustomChallenge, FileMetadata, - ForestLeaf, HashT, KeyProof, KeyProofs, MainStorageProviderId, ProofsDealerProviderId, - Proven, RandomnessOutput, StorageProof, StorageProofsMerkleTrieLayout, BCSV_KEY_TYPE, - FILE_CHUNK_SIZE, - }, -}; +use shc_common::{consts::CURRENT_FOREST_KEY, types::*}; use shc_file_manager::traits::{ExcludeType, FileDataTrie, FileStorage, FileStorageError}; use shc_forest_manager::traits::{ForestStorage, ForestStorageHandler}; use sp_core::{sr25519::Pair as Sr25519Pair, Encode, Pair, H256}; @@ -323,6 +315,7 @@ where BlockNumber, ChunkId, BucketId, + StorageRequestMetadata, >, FL: FileStorage + Send + Sync, FSH: ForestStorageHandler + Send + Sync + 'static, diff --git a/node/src/rpc.rs b/node/src/rpc.rs index 792da3810..6c41cfa41 100644 --- a/node/src/rpc.rs +++ b/node/src/rpc.rs @@ -15,10 +15,7 @@ use sc_consensus_manual_seal::{ }; use sc_rpc::DenyUnsafe; use sc_transaction_pool_api::TransactionPool; -use shc_common::types::{ - BackupStorageProviderId, BlockNumber, BucketId, ChunkId, CustomChallenge, ForestLeaf, - MainStorageProviderId, ProofsDealerProviderId, RandomnessOutput, -}; +use shc_common::types::*; use shc_forest_manager::traits::ForestStorageHandler; use shc_rpc::{StorageHubClientApiServer, StorageHubClientRpc, StorageHubClientRpcConfig}; use sp_api::ProvideRuntimeApi; @@ -73,6 +70,7 @@ where BlockNumber, ChunkId, BucketId, + StorageRequestMetadata, >, P: TransactionPool + Send + Sync + 'static, FL: FileStorageT, diff --git a/node/src/tasks/bsp_charge_fees.rs b/node/src/tasks/bsp_charge_fees.rs index d69ad866d..e347b7941 100644 --- a/node/src/tasks/bsp_charge_fees.rs +++ b/node/src/tasks/bsp_charge_fees.rs @@ -272,6 +272,9 @@ where .map_err(|e| anyhow!("Failed to get metadata from Forest: {:?}", e))?; if !user_files.is_empty() { + // We only take the first file of the list in order to generate a proof submit it with an extrinsic and then release the lock to process the next file and generate the next proof. + // It is not ideal because it means one extrinsic per file but batch deletion is not yet implemented. + // TODO: Improve it once batch deletion is implemented. let (file_key, metadata) = user_files.first().expect("User files is not empty"); let bucket_id = H256::from_slice(metadata.bucket_id().as_ref()); let location = sp_runtime::BoundedVec::truncate_from(metadata.location().clone()); diff --git a/node/src/tasks/user_sends_file.rs b/node/src/tasks/user_sends_file.rs index 892f1dd62..0f1189c97 100644 --- a/node/src/tasks/user_sends_file.rs +++ b/node/src/tasks/user_sends_file.rs @@ -317,7 +317,7 @@ where { warn!( target: LOG_TARGET, - "Batch upload rejected by peer {:?}, retrying... (attempt {})", + "Final batch upload rejected by peer {:?}, retrying... (attempt {})", peer_id, retry_attempts + 1 ); @@ -326,7 +326,27 @@ where // Wait for a short time before retrying tokio::time::sleep(std::time::Duration::from_secs(1)).await; } - Err(RequestError::RequestFailure(RequestFailure::Refused)) => { + Err(RequestError::RequestFailure(RequestFailure::Network(_))) + | Err(RequestError::RequestFailure(RequestFailure::NotConnected)) + if retry_attempts < 10 => + { + warn!( + target: LOG_TARGET, + "Unable to upload final batch to peer {:?}, retrying... (attempt {})", + peer_id, + retry_attempts + 1 + ); + retry_attempts += 1; + + // Wait a bit for the MSP to be online + self.storage_hub_handler + .blockchain + .wait_for_num_blocks(5) + .await?; + } + Err(RequestError::RequestFailure(RequestFailure::Refused)) + | Err(RequestError::RequestFailure(RequestFailure::Network(_))) + | Err(RequestError::RequestFailure(RequestFailure::NotConnected)) => { // Return an error if the provider refused to answer. return Err(anyhow::anyhow!("Failed to send file {:?}", file_key)); } @@ -388,7 +408,7 @@ where .upload_request(peer_id, file_key.as_ref().into(), proof.clone(), None) .await; - match upload_response { + match upload_response.as_ref() { Ok(r) => { debug!( target: LOG_TARGET, @@ -421,7 +441,27 @@ where // Wait for a short time before retrying tokio::time::sleep(std::time::Duration::from_secs(1)).await; } - Err(RequestError::RequestFailure(RequestFailure::Refused)) => { + Err(RequestError::RequestFailure(RequestFailure::Network(_))) + | Err(RequestError::RequestFailure(RequestFailure::NotConnected)) + if retry_attempts < 10 => + { + warn!( + target: LOG_TARGET, + "Unable to upload final batch to peer {:?}, retrying... (attempt {})", + peer_id, + retry_attempts + 1 + ); + retry_attempts += 1; + + // Wait a bit for the MSP to be online + self.storage_hub_handler + .blockchain + .wait_for_num_blocks(5) + .await?; + } + Err(RequestError::RequestFailure(RequestFailure::Refused)) + | Err(RequestError::RequestFailure(RequestFailure::Network(_))) + | Err(RequestError::RequestFailure(RequestFailure::NotConnected)) => { // Return an error if the provider refused to answer. return Err(anyhow::anyhow!("Failed to send file {:?}", file_key)); } diff --git a/pallets/file-system/runtime-api/Cargo.toml b/pallets/file-system/runtime-api/Cargo.toml index c95521948..d2148f3af 100644 --- a/pallets/file-system/runtime-api/Cargo.toml +++ b/pallets/file-system/runtime-api/Cargo.toml @@ -18,8 +18,10 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { workspace = true, features = ["derive"] } scale-info = { workspace = true } sp-api = { workspace = true } +sp-core = { workspace = true } sp-runtime = { workspace = true } +sp-std = { workspace = true } [features] default = ["std"] -std = ["codec/std", "sp-api/std", "sp-runtime/std"] +std = ["codec/std", "sp-api/std", "sp-runtime/std", "sp-std/std"] diff --git a/pallets/file-system/runtime-api/src/lib.rs b/pallets/file-system/runtime-api/src/lib.rs index b1b708985..6b7d552ba 100644 --- a/pallets/file-system/runtime-api/src/lib.rs +++ b/pallets/file-system/runtime-api/src/lib.rs @@ -3,7 +3,9 @@ use codec::{Codec, Decode, Encode}; use scale_info::prelude::vec::Vec; use scale_info::TypeInfo; +use sp_core::H256; use sp_runtime::RuntimeDebug; +use sp_std::collections::btree_map::BTreeMap; /// Error type for the `is_storage_request_open_to_volunteers` runtime API call. #[derive(Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] @@ -53,9 +55,15 @@ pub enum GenericApplyDeltaEventInfoError { DecodeError, } +/// Error type for the `pending_storage_requests_by_msp`. +#[derive(Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] +pub enum StorageRequestsByMspError { + FailedToRetrieveStorageRequests, +} + sp_api::decl_runtime_apis! { #[api_version(1)] - pub trait FileSystemApi + pub trait FileSystemApi where BackupStorageProviderId: Codec, MainStorageProviderId: Codec, @@ -63,11 +71,13 @@ sp_api::decl_runtime_apis! { TickNumber: Codec, ChunkId: Codec, GenericApplyDeltaEventInfo: Codec, + StorageRequestMetadata: Codec, { fn is_storage_request_open_to_volunteers(file_key: FileKey) -> Result; fn query_earliest_file_volunteer_tick(bsp_id: BackupStorageProviderId, file_key: FileKey) -> Result; fn query_bsp_confirm_chunks_to_prove_for_file(bsp_id: BackupStorageProviderId, file_key: FileKey) -> Result, QueryBspConfirmChunksToProveForFileError>; fn query_msp_confirm_chunks_to_prove_for_file(msp_id: MainStorageProviderId, file_key: FileKey) -> Result, QueryMspConfirmChunksToProveForFileError>; fn decode_generic_apply_delta_event_info(encoded_event_info: Vec) -> Result; + fn pending_storage_requests_by_msp(msp_id: MainStorageProviderId) -> BTreeMap; } } diff --git a/pallets/file-system/src/utils.rs b/pallets/file-system/src/utils.rs index 6d2033f76..8c4353686 100644 --- a/pallets/file-system/src/utils.rs +++ b/pallets/file-system/src/utils.rs @@ -34,6 +34,7 @@ use shp_traits::{ ReadBucketsInterface, ReadProvidersInterface, ReadStorageProvidersInterface, ReadUserSolvencyInterface, TrieAddMutation, TrieRemoveMutation, }; +use sp_std::collections::btree_map::BTreeMap; use crate::{ pallet, @@ -2841,6 +2842,22 @@ where ) -> Result, codec::Error> { BucketIdFor::::decode(&mut encoded_event_info) } + + pub fn pending_storage_requests_by_msp( + msp_id: ProviderIdFor, + ) -> BTreeMap, StorageRequestMetadata> { + // Get the storage requests for a specific MSP + StorageRequests::::iter() + .filter(|(_, metadata)| { + if let Some(msp) = metadata.msp { + msp.0 == msp_id && !msp.1 + } else { + false + } + }) + .map(|(file_key, metadata)| (file_key, metadata)) + .collect() + } } mod hooks { diff --git a/runtime/src/apis.rs b/runtime/src/apis.rs index 99d4a53b0..7a02d1445 100644 --- a/runtime/src/apis.rs +++ b/runtime/src/apis.rs @@ -4,6 +4,7 @@ use frame_support::{ weights::Weight, }; use pallet_aura::Authorities; +use pallet_file_system::types::StorageRequestMetadata; use pallet_file_system_runtime_api::*; use pallet_payment_streams_runtime_api::*; use pallet_proofs_dealer::types::{ @@ -24,7 +25,7 @@ use sp_runtime::{ transaction_validity::{TransactionSource, TransactionValidity}, ApplyExtrinsicResult, ExtrinsicInclusionMode, }; -use sp_std::prelude::Vec; +use sp_std::{collections::btree_map::BTreeMap, prelude::Vec}; use sp_version::RuntimeVersion; use xcm::{ latest::prelude::AssetId, VersionedAssetId, VersionedAssets, VersionedLocation, VersionedXcm, @@ -328,7 +329,7 @@ impl_runtime_apis! { } } - impl pallet_file_system_runtime_api::FileSystemApi, MainStorageProviderId, H256, BlockNumber, ChunkId, BucketId> for Runtime { + impl pallet_file_system_runtime_api::FileSystemApi, MainStorageProviderId, H256, BlockNumber, ChunkId, BucketId, StorageRequestMetadata> for Runtime { fn is_storage_request_open_to_volunteers(file_key: H256) -> Result { FileSystem::is_storage_request_open_to_volunteers(file_key) } @@ -345,9 +346,13 @@ impl_runtime_apis! { FileSystem::query_msp_confirm_chunks_to_prove_for_file(msp_id, file_key) } - fn decode_generic_apply_delta_event_info(encoded_event_info: Vec) -> Result, GenericApplyDeltaEventInfoError> { + fn decode_generic_apply_delta_event_info(encoded_event_info: Vec) -> Result, GenericApplyDeltaEventInfoError> { FileSystem::decode_generic_apply_delta_event_info(encoded_event_info) } + + fn pending_storage_requests_by_msp(msp_id: MainStorageProviderId) -> BTreeMap> { + FileSystem::pending_storage_requests_by_msp(msp_id) + } } impl pallet_payment_streams_runtime_api::PaymentStreamsApi, Balance, AccountId> for Runtime { diff --git a/test/suites/integration/msp/catch-up-storage.test.ts b/test/suites/integration/msp/catch-up-storage.test.ts new file mode 100644 index 000000000..4eb7475bc --- /dev/null +++ b/test/suites/integration/msp/catch-up-storage.test.ts @@ -0,0 +1,136 @@ +import assert, { strictEqual } from "node:assert"; +import { describeMspNet, shUser, type EnrichedBspApi, waitFor, sleep } from "../../../util"; + +describeMspNet( + "MSP catching up with chain and volunteering for storage request", + { initialised: false }, + ({ before, createMsp1Api, it, createUserApi, createApi }) => { + let userApi: EnrichedBspApi; + let mspApi: EnrichedBspApi; + + before(async () => { + userApi = await createUserApi(); + const maybeMspApi = await createMsp1Api(); + + assert(maybeMspApi, "MSP API not available"); + mspApi = maybeMspApi; + }); + + it("Network launches and can be queried", async () => { + const userNodePeerId = await userApi.rpc.system.localPeerId(); + strictEqual(userNodePeerId.toString(), userApi.shConsts.NODE_INFOS.user.expectedPeerId); + + const mspNodePeerId = await mspApi.rpc.system.localPeerId(); + strictEqual(mspNodePeerId.toString(), userApi.shConsts.NODE_INFOS.msp1.expectedPeerId); + }); + + it( + "MSP accept storage request after catching up with blockchain and user properly retry sending file", + { timeout: 50000 }, + async () => { + const source = "res/whatsup.jpg"; + const destination = "test/smile.jpg"; + const bucketName = "trying-things"; + + // Stop the msp container so it will be behind when we restart the node. + // TODO: clearLogs is not working, fix it. + // await clearLogs({ containerName: "docker-sh-msp-1" }); + await userApi.docker.pauseContainer("docker-sh-msp-1"); + + const newBucketEventEvent = await userApi.createBucket(bucketName); + const newBucketEventDataBlob = + userApi.events.fileSystem.NewBucket.is(newBucketEventEvent) && newBucketEventEvent.data; + + assert(newBucketEventDataBlob, "Event doesn't match Type"); + + await userApi.rpc.storagehubclient.loadFileInStorage( + source, + destination, + userApi.shConsts.NODE_INFOS.user.AddressId, + newBucketEventDataBlob.bucketId + ); + + await userApi.block.seal({ + calls: [ + userApi.tx.fileSystem.issueStorageRequest( + newBucketEventDataBlob.bucketId, + destination, + userApi.shConsts.TEST_ARTEFACTS[source].fingerprint, + userApi.shConsts.TEST_ARTEFACTS[source].size, + userApi.shConsts.DUMMY_MSP_ID, + [userApi.shConsts.NODE_INFOS.user.expectedPeerId], + { + Basic: null + } + ) + ], + signer: shUser + }); + + const { event } = await userApi.assert.eventPresent("fileSystem", "NewStorageRequest"); + const newStorageRequestDataBlob = + userApi.events.fileSystem.NewStorageRequest.is(event) && event.data; + assert( + newStorageRequestDataBlob, + "NewStorageRequest event data does not match expected type" + ); + + // Advancing 10 blocks to see if MSP catchup + await userApi.block.skip(10); + + // Closing mspApi gracefully before restarting the container + // IMPORTANT: If this is not done, the api connection cannot close properly and the test + // runner will hang. + await mspApi.disconnect(); + + // Restarting the MSP container. This will start the Substrate node from scratch. + await userApi.docker.restartContainer({ containerName: "docker-sh-msp-1" }); + + // TODO: Wait for the container logs of starting up + await userApi.docker.waitForLog({ + searchString: "💤 Idle (3 peers)", + containerName: "docker-sh-msp-1", + tail: 10 + }); + + // Doesn't work without this because there is no log that tell us when the websocket is ready + await sleep(15000); + + // Creating a new MSP API to connect to the newly restarted container. + const newMspApi = await createApi( + `ws://127.0.0.1:${userApi.shConsts.NODE_INFOS.msp1.port}` + ); + + console.log("Connected"); + + // Waiting for the MSP node to be in sync with the chain. + await userApi.rpc.engine.createBlock(true, true); + + await userApi.docker.waitForLog({ + searchString: "🥱 Handling coming out of sync mode", + containerName: "docker-sh-msp-1" + }); + + await userApi.block.skip(4); // user retry every 5 blocks. The one we created before and this one + + await userApi.docker.waitForLog({ + searchString: + 'File upload complete. Peer PeerId("12D3KooWSUvz8QM5X4tfAaSLErAZjR2puojo16pULBHyqTMGKtNV") has the entire file', + containerName: "docker-sh-user-1" + }); + + await waitFor({ + lambda: async () => + (await newMspApi.rpc.storagehubclient.isFileInFileStorage(event.data.fileKey)) + .isFileFound + }); + + await userApi.block.seal(); + await userApi.assert.eventPresent("fileSystem", "MspAcceptedStorageRequest"); + + // IMPORTANT!! Without this the test suite never finish + newMspApi.disconnect(); + } + ); + } +); diff --git a/test/util/bspNet/docker.ts b/test/util/bspNet/docker.ts index a0d4d2ec3..6cdefaa0e 100644 --- a/test/util/bspNet/docker.ts +++ b/test/util/bspNet/docker.ts @@ -182,6 +182,21 @@ export const restartContainer = async (options: { await container.restart(); }; +export const clearLogs = async (options: { + containerName: string; +}) => { + const docker = new Docker(); + const container = docker.getContainer(options.containerName); + const exec = await container.exec({ + AttachStdout: true, + AttachStderr: true, + Cmd: ["sh", "-c", `> /var/lib/docker/containers/${options.containerName}/*.log`] + }); + + await exec.start({}); + console.log(`Logs cleared for container ${options.containerName}`); +}; + export const resumeContainer = async (options: { containerName: string; }) => { @@ -232,13 +247,14 @@ export const waitForLog = async (options: { searchString: string; containerName: string; timeout?: number; + tail?: number; }): Promise => { return new Promise((resolve, reject) => { const docker = new Docker(); const container = docker.getContainer(options.containerName); container.logs( - { follow: true, stdout: true, stderr: true, tail: undefined, timestamps: false }, + { follow: true, stdout: true, stderr: true, tail: options.tail, timestamps: false }, // set tail default to 10 to get the 10 last lines of logs printed (err, stream) => { if (err) { return reject(err); diff --git a/test/util/bspNet/waits.ts b/test/util/bspNet/waits.ts index 392de8712..f21b237d4 100644 --- a/test/util/bspNet/waits.ts +++ b/test/util/bspNet/waits.ts @@ -357,6 +357,7 @@ export const waitForBspToCatchUpToChainTip = async ( await sleep(delay); const syncedBestBlock = await syncedApi.rpc.chain.getHeader(); const bspBehindBestBlock = await bspBehindApi.rpc.chain.getHeader(); + assert( syncedBestBlock.hash.toString() === bspBehindBestBlock.hash.toString(), "BSP did not catch up to the chain tip" diff --git a/xcm-simulator/src/storagehub/apis.rs b/xcm-simulator/src/storagehub/apis.rs index 6fc7a93ef..ce4829107 100644 --- a/xcm-simulator/src/storagehub/apis.rs +++ b/xcm-simulator/src/storagehub/apis.rs @@ -5,6 +5,7 @@ use frame_support::{ weights::Weight, }; use pallet_aura::Authorities; +use pallet_file_system::types::StorageRequestMetadata; use pallet_file_system_runtime_api::*; use pallet_payment_streams_runtime_api::*; use pallet_proofs_dealer::types::{ @@ -25,6 +26,7 @@ use sp_runtime::{ transaction_validity::{TransactionSource, TransactionValidity}, ApplyExtrinsicResult, ExtrinsicInclusionMode, }; +use sp_std::collections::btree_map::BTreeMap; use sp_std::prelude::Vec; use sp_version::RuntimeVersion; use xcm_runtime_apis::{ @@ -326,7 +328,7 @@ impl_runtime_apis! { } } - impl pallet_file_system_runtime_api::FileSystemApi, MainStorageProviderId, H256, BlockNumber, ChunkId, BucketId> for Runtime { + impl pallet_file_system_runtime_api::FileSystemApi, MainStorageProviderId, H256, BlockNumber, ChunkId, BucketId, StorageRequestMetadata> for Runtime { fn is_storage_request_open_to_volunteers(file_key: H256) -> Result { FileSystem::is_storage_request_open_to_volunteers(file_key) } @@ -346,6 +348,10 @@ impl_runtime_apis! { fn decode_generic_apply_delta_event_info(encoded_event_info: Vec) -> Result, GenericApplyDeltaEventInfoError> { FileSystem::decode_generic_apply_delta_event_info(encoded_event_info) } + + fn pending_storage_requests_by_msp(msp_id: MainStorageProviderId) -> BTreeMap> { + FileSystem::pending_storage_requests_by_msp(msp_id) + } } impl pallet_payment_streams_runtime_api::PaymentStreamsApi, Balance, AccountId> for Runtime {