From 3d68c857ac804f798b74649d2468df72df3a06f0 Mon Sep 17 00:00:00 2001 From: Maria Kuklina <101095419+kmd-fl@users.noreply.github.com> Date: Fri, 23 Feb 2024 19:59:44 +0100 Subject: [PATCH] feat(particle-vault)!: introduce new particle vault path format (#2098) --- Cargo.lock | 5 +- Makefile | 34 ++++++ aquamarine/src/actor.rs | 11 +- aquamarine/src/particle_data_store.rs | 71 +++++++---- aquamarine/src/plumber.rs | 10 +- crates/fs-utils/src/lib.rs | 11 +- .../file_share/artifacts/file_share.wasm | Bin 126041 -> 126574 bytes crates/nox-tests/tests/file_share/src/main.rs | 4 +- crates/system-services/Cargo.toml | 2 +- crates/system-services/src/deployer.rs | 5 +- particle-builtins/src/builtins.rs | 20 ++- particle-execution/src/particle_vault.rs | 115 +++++++++++++----- particle-modules/Cargo.toml | 1 + particle-modules/src/modules.rs | 9 +- particle-services/src/app_services.rs | 18 ++- 15 files changed, 233 insertions(+), 83 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 69c252513b..475d473b5d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -229,9 +229,9 @@ checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca" [[package]] name = "aqua-ipfs-distro" -version = "0.5.30" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a612e6a1d6e95b2af04a7deca1d40c1bf1e838308bd186344399b6c996bb535f" +checksum = "3b8b5b0dd31ea5a1670e2d1ad0f5f17bcb2baef51669c077158fe517df1bb21f" dependencies = [ "built 0.7.1", "maplit", @@ -5741,6 +5741,7 @@ dependencies = [ "cid 0.11.0", "eyre", "fluence-app-service", + "fluence-libp2p", "fs-utils", "fstrings", "itertools 0.12.1", diff --git a/Makefile b/Makefile index 9384b6d943..c27aa7152d 100644 --- a/Makefile +++ b/Makefile @@ -38,6 +38,40 @@ server: particle_reap=debug" \ cargo run --release -p nox +spell: + WASM_LOG="trace" \ + RUST_LOG="info,\ + aquamarine::aqua_runtime=off,\ + ipfs_effector=debug,\ + ipfs_pure=debug,\ + run-console=debug,\ + system_services=debug,\ + spell_even_bus=trace,\ + marine_core::module::marine_module=info,\ + aquamarine::log=debug,\ + aquamarine=warn,\ + tokio_threadpool=info,\ + tokio_reactor=info,\ + mio=info,\ + tokio_io=info,\ + soketto=info,\ + yamux=info,\ + multistream_select=info,\ + libp2p_secio=info,\ + libp2p_websocket::framed=info,\ + libp2p_ping=info,\ + libp2p_core::upgrade::apply=info,\ + libp2p_kad::kbucket=info,\ + cranelift_codegen=info,\ + wasmer_wasi=info,\ + cranelift_codegen=info,\ + wasmer_wasi=info,\ + wasmtime_cranelift=off,\ + wasmtime_jit=off,\ + particle_protocol=off,\ + particle_reap=debug" \ + cargo run --release -p nox + local-env: docker compose -f docker-compose.yml up -d diff --git a/aquamarine/src/actor.rs b/aquamarine/src/actor.rs index b5fcebfa9b..7d896814fc 100644 --- a/aquamarine/src/actor.rs +++ b/aquamarine/src/actor.rs @@ -63,6 +63,8 @@ pub struct Actor { /// Particles and call results will be processed in the security scope of this peer id /// It's either `host_peer_id` or local worker peer id current_peer_id: PeerId, + /// TODO: for the clean-up, I don't think we need it here! + particle_token: String, key_pair: KeyPair, data_store: Arc, spawner: Spawner, @@ -74,10 +76,13 @@ where RT: AquaRuntime, F: ParticleFunctionStatic, { + // TODO: temporary (I hope), need to do smth clever with particle_token + #[allow(clippy::too_many_arguments)] pub fn new( particle: &Particle, functions: Functions, current_peer_id: PeerId, + particle_token: String, key_pair: KeyPair, data_store: Arc, deal_id: Option, @@ -95,6 +100,7 @@ where ..particle.clone() }, current_peer_id, + particle_token, key_pair, data_store, spawner, @@ -110,10 +116,11 @@ where self.future.is_some() } - pub fn cleanup_key(&self) -> (String, PeerId, Vec) { + pub fn cleanup_key(&self) -> (String, PeerId, Vec, String) { let particle_id = self.particle.id.clone(); let signature = self.particle.signature.clone(); - (particle_id, self.current_peer_id, signature) + let token = self.particle_token.clone(); + (particle_id, self.current_peer_id, signature, token) } pub fn mailbox_size(&self) -> usize { diff --git a/aquamarine/src/particle_data_store.rs b/aquamarine/src/particle_data_store.rs index 8bb03e9544..3293e4ff80 100644 --- a/aquamarine/src/particle_data_store.rs +++ b/aquamarine/src/particle_data_store.rs @@ -122,28 +122,34 @@ impl ParticleDataStore { Ok(data) } - pub async fn batch_cleanup_data(&self, cleanup_keys: Vec<(String, PeerId, Vec)>) { + pub async fn batch_cleanup_data(&self, cleanup_keys: Vec<(String, PeerId, Vec, String)>) { let futures: FuturesUnordered<_> = cleanup_keys .into_iter() - .map(|(particle_id, peer_id, signature)| async move { - let peer_id = peer_id.to_string(); - tracing::debug!( - target: "particle_reap", - particle_id = particle_id, worker_id = peer_id, - "Reaping particle's actor" - ); - - if let Err(err) = self - .cleanup_data(particle_id.as_str(), peer_id.as_str(), &signature) - .await - { - tracing::warn!( - particle_id = particle_id, - "Error cleaning up after particle {:?}", - err + .map( + |(particle_id, peer_id, signature, particle_token)| async move { + tracing::debug!( + target: "particle_reap", + particle_id = particle_id, worker_id = peer_id.to_base58(), + "Reaping particle's actor" ); - } - }) + + if let Err(err) = self + .cleanup_data( + particle_id.as_str(), + peer_id, + &signature, + particle_token.as_str(), + ) + .await + { + tracing::warn!( + particle_id = particle_id, + "Error cleaning up after particle {:?}", + err + ); + } + }, + ) .collect(); let _results: Vec<_> = futures.collect().await; } @@ -151,11 +157,12 @@ impl ParticleDataStore { async fn cleanup_data( &self, particle_id: &str, - current_peer_id: &str, + current_peer_id: PeerId, signature: &[u8], + particle_token: &str, ) -> Result<()> { tracing::debug!(target: "particle_reap", particle_id = particle_id, "Cleaning up particle data for particle"); - let path = self.data_file(particle_id, current_peer_id, signature); + let path = self.data_file(particle_id, ¤t_peer_id.to_base58(), signature); match tokio::fs::remove_file(&path).await { Ok(_) => Ok(()), // ignore NotFound @@ -163,7 +170,9 @@ impl ParticleDataStore { Err(err) => Err(DataStoreError::CleanupData(err)), }?; - self.vault.cleanup(particle_id).await?; + self.vault + .cleanup(current_peer_id, particle_id, particle_token) + .await?; Ok(()) } @@ -318,6 +327,7 @@ mod tests { use crate::ParticleDataStore; use avm_server::avm_runner::RawAVMOutcome; use avm_server::{CallRequests, CallResults, CallServiceResult}; + use fluence_libp2p::PeerId; use std::path::PathBuf; use std::time::Duration; @@ -501,17 +511,24 @@ mod tests { .expect("Failed to initialize"); let particle_id = "test_particle"; - let current_peer_id = "test_peer"; + let particle_token = "test_token"; + let current_peer_id = PeerId::random(); + let current_peer_id_str = current_peer_id.to_base58(); let signature: &[u8] = &[]; let data = b"test_data"; particle_data_store - .store_data(data, particle_id, current_peer_id, signature) + .store_data(data, particle_id, ¤t_peer_id_str, signature) .await .expect("Failed to store data"); - let data_file_path = particle_data_store.data_file(particle_id, current_peer_id, signature); - let vault_path = temp_dir_path.join("vault").join(particle_id); + let data_file_path = + particle_data_store.data_file(particle_id, ¤t_peer_id_str, signature); + let vault_path = particle_data_store.vault.real_particle_vault( + current_peer_id, + particle_id, + particle_token, + ); tokio::fs::create_dir_all(&vault_path) .await .expect("Failed to create vault dir"); @@ -519,7 +536,7 @@ mod tests { assert!(vault_path.exists()); let cleanup_result = particle_data_store - .cleanup_data(particle_id, current_peer_id, signature) + .cleanup_data(particle_id, current_peer_id, signature, particle_token) .await; assert!(cleanup_result.is_ok()); diff --git a/aquamarine/src/plumber.rs b/aquamarine/src/plumber.rs index 208a9eae06..dc189d1c5f 100644 --- a/aquamarine/src/plumber.rs +++ b/aquamarine/src/plumber.rs @@ -186,8 +186,11 @@ impl Plumber { &self.key_storage.root_key_pair, &particle.particle.signature, )?; - let params = - ParticleParams::clone_from(particle.as_ref(), peer_scope, particle_token); + let params = ParticleParams::clone_from( + particle.as_ref(), + peer_scope, + particle_token.clone(), + ); let functions = Functions::new(params, builtins.clone()); let key_pair = self .key_storage @@ -221,6 +224,7 @@ impl Plumber { particle.as_ref(), functions, current_peer_id, + particle_token, key_pair, data_store, deal_id, @@ -329,7 +333,7 @@ impl Plumber { // do not schedule task if another in progress if self.cleanup_future.is_none() { // Remove expired actors - let mut cleanup_keys: Vec<(String, PeerId, Vec)> = + let mut cleanup_keys: Vec<(String, PeerId, Vec, String)> = Vec::with_capacity(MAX_CLEANUP_KEYS_SIZE); let now = now_ms(); self.actors.retain(|_, actor| { diff --git a/crates/fs-utils/src/lib.rs b/crates/fs-utils/src/lib.rs index fac12ec9cb..3cc97a15ca 100644 --- a/crates/fs-utils/src/lib.rs +++ b/crates/fs-utils/src/lib.rs @@ -30,9 +30,10 @@ use eyre::{eyre, Context}; use futures_util::StreamExt; use std::fmt::Debug; use std::fs; -use std::fs::Permissions; +use std::fs::{DirBuilder, Permissions}; use std::future::Future; use std::io::ErrorKind; +use std::os::unix::fs::DirBuilderExt; use std::path::{Path, PathBuf}; use std::thread::available_parallelism; use thiserror::Error; @@ -77,6 +78,14 @@ pub fn create_dir + Debug>(dir: P) -> Result<(), std::io::Error> .map_err(|err| std::io::Error::new(err.kind(), format!("{err:?}: {dir:?}"))) } +pub fn create_dir_write_only + Debug>(dir: P) -> Result<(), std::io::Error> { + DirBuilder::new() + .recursive(true) + .mode(0o333) + .create(&dir) + .map_err(|err| std::io::Error::new(err.kind(), format!("{err:?}: {dir:?}"))) +} + pub fn remove_dirs(dirs: &[Item]) -> Result<(), std::io::Error> where Item: AsRef + Debug, diff --git a/crates/nox-tests/tests/file_share/artifacts/file_share.wasm b/crates/nox-tests/tests/file_share/artifacts/file_share.wasm index 9ac3ec38ef26e21bbf2df536f20628d4134ef311..363949b6e369e26e928a5302d639427cf503902f 100755 GIT binary patch delta 22927 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 delta 21849 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%0Aq6j0`?{f_1 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 QRX%!v2DbFtPv>X;AIK{FWdHyG diff --git a/crates/nox-tests/tests/file_share/src/main.rs b/crates/nox-tests/tests/file_share/src/main.rs index 239775d4c4..57ad7f0a32 100644 --- a/crates/nox-tests/tests/file_share/src/main.rs +++ b/crates/nox-tests/tests/file_share/src/main.rs @@ -78,7 +78,9 @@ fn store_file(contents: impl AsRef<[u8]>) -> (String, String) { fn vault_dir() -> PathBuf { let particle_id = get_call_parameters().particle.id; - let vault = Path::new("/tmp").join("vault").join(particle_id); + let token = get_call_parameters().particle.token; + let path = format!("{}-{}", particle_id, token); + let vault = Path::new("/tmp").join("vault").join(path); vault } diff --git a/crates/system-services/Cargo.toml b/crates/system-services/Cargo.toml index d1839ff7a8..1312238a26 100644 --- a/crates/system-services/Cargo.toml +++ b/crates/system-services/Cargo.toml @@ -6,7 +6,7 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -aqua-ipfs-distro = "=0.5.30" +aqua-ipfs-distro = "=0.6.0" decider-distro = "=0.6.7" registry-distro = "=0.9.4" trust-graph-distro = "=0.4.11" diff --git a/crates/system-services/src/deployer.rs b/crates/system-services/src/deployer.rs index f99da447d1..9d6102f6ef 100644 --- a/crates/system-services/src/deployer.rs +++ b/crates/system-services/src/deployer.rs @@ -184,10 +184,11 @@ impl Deployer { } let trigger_config = spell_event_bus::api::from_user_config(&spell_distro.trigger_config)?; - let params = CallParams::local( + let params = CallParams::new( + self.host_peer_id, PeerScope::Host, spell_id.to_string(), - self.host_peer_id, + Some(format!("spell_{spell_id}_0")), DEPLOYER_TTL, ); // update trigger config diff --git a/particle-builtins/src/builtins.rs b/particle-builtins/src/builtins.rs index 58bf2484c7..735b3141f4 100644 --- a/particle-builtins/src/builtins.rs +++ b/particle-builtins/src/builtins.rs @@ -621,6 +621,7 @@ where let module_hash = self.modules.add_module_from_vault( &self.services.vault, + self.scopes.to_peer_id(params.peer_scope), module_path, config, params, @@ -649,6 +650,7 @@ where let config = ModuleRepository::load_module_config_from_vault( &self.services.vault, + self.scopes.to_peer_id(params.peer_scope), config_path, params, )?; @@ -694,10 +696,11 @@ where let mut args = args.function_args.into_iter(); let blueprint_path: String = Args::next("blueprint_path", &mut args)?; - let data = self - .services - .vault - .cat_slice(¶ms, Path::new(&blueprint_path))?; + let current_peer_id = self.scopes.to_peer_id(params.peer_scope); + let data = + self.services + .vault + .cat_slice(current_peer_id, ¶ms, Path::new(&blueprint_path))?; let blueprint = AddBlueprint::decode(&data).map_err(|err| { JError::new(format!( "Error parsing blueprint from vault {blueprint_path:?}: {err}" @@ -986,7 +989,11 @@ where let mut args = args.function_args.into_iter(); let data: String = Args::next("data", &mut args)?; let name = uuid(); - let virtual_path = self.services.vault.put(¶ms, name, &data)?; + let current_peer_id = self.scopes.to_peer_id(params.peer_scope); + let virtual_path = self + .services + .vault + .put(current_peer_id, ¶ms, name, &data)?; Ok(JValue::String(virtual_path.display().to_string())) } @@ -994,9 +1001,10 @@ where fn vault_cat(&self, args: Args, params: ParticleParams) -> Result { let mut args = args.function_args.into_iter(); let path: String = Args::next("path", &mut args)?; + let current_peer_id = self.scopes.to_peer_id(params.peer_scope); self.services .vault - .cat(¶ms, Path::new(&path)) + .cat(current_peer_id, ¶ms, Path::new(&path)) .map(JValue::String) .map_err(|_| JError::new(format!("Error reading vault file `{path}`"))) } diff --git a/particle-execution/src/particle_vault.rs b/particle-execution/src/particle_vault.rs index b4c2fa8626..5e133c8d1c 100644 --- a/particle-execution/src/particle_vault.rs +++ b/particle-execution/src/particle_vault.rs @@ -20,9 +20,10 @@ use std::io::ErrorKind; use std::path; use std::path::{Path, PathBuf}; +use fluence_libp2p::PeerId; use thiserror::Error; -use fs_utils::create_dir; +use fs_utils::{create_dir, create_dir_write_only}; use crate::ParticleParams; use crate::VaultError::WrongVault; @@ -40,14 +41,34 @@ impl ParticleVault { Self { vault_dir } } + pub fn real_worker_particle_vault(&self, peer_id: PeerId) -> PathBuf { + self.vault_dir.join(peer_id.to_base58()) + } + /// Returns Particle File Vault path on Nox's filesystem - pub fn particle_vault(&self, particle_id: &str) -> PathBuf { - self.vault_dir.join(particle_id) + pub fn real_particle_vault( + &self, + peer_id: PeerId, + particle_id: &str, + particle_token: &str, + ) -> PathBuf { + self.real_worker_particle_vault(peer_id) + .join(Self::format_particle_directory_name( + particle_id, + particle_token, + )) } /// Returns Particle File Vault path on Marine's filesystem (ie how it would look like inside service) - pub fn virtual_particle_vault(&self, particle_id: &str) -> PathBuf { - Path::new(VIRTUAL_PARTICLE_VAULT_PREFIX).join(particle_id) + pub fn virtual_particle_vault(&self, particle_id: &str, particle_token: &str) -> PathBuf { + Path::new(VIRTUAL_PARTICLE_VAULT_PREFIX).join(Self::format_particle_directory_name( + particle_id, + particle_token, + )) + } + + fn format_particle_directory_name(id: &str, token: &str) -> String { + format!("{}-{}", id, token) } pub async fn initialize(&self) -> Result<(), VaultError> { @@ -58,44 +79,51 @@ impl ParticleVault { Ok(()) } - pub fn create(&self, particle: &ParticleParams) -> Result<(), VaultError> { - let path = self.particle_vault(&particle.id); - create_dir(path).map_err(CreateVault)?; - - Ok(()) + pub fn initialize_worker(&self, worker_id: PeerId) -> Result<(), VaultError> { + let path = self.real_worker_particle_vault(worker_id); + create_dir_write_only(path).map_err(InitializeVault) } - pub fn exists(&self, particle: &ParticleParams) -> bool { - self.particle_vault(&particle.id).exists() + pub fn create( + &self, + current_peer_id: PeerId, + particle_id: &str, + particle_token: &str, + ) -> Result<(), VaultError> { + let path = self.real_particle_vault(current_peer_id, particle_id, particle_token); + create_dir(path).map_err(CreateVault)?; + Ok(()) } pub fn put( &self, + current_peer_id: PeerId, particle: &ParticleParams, filename: String, payload: &str, ) -> Result { - let vault_dir = self.particle_vault(&particle.id); + let vault_dir = self.real_particle_vault(current_peer_id, &particle.id, &particle.token); // Note that we can't use `to_real_path` here since the target file cannot exist yet, // but `to_real_path` do path normalization which requires existence of the file to resolve // symlinks. let real_path = vault_dir.join(&filename); if let Some(parent_path) = real_path.parent() { - create_dir(parent_path).map_err(CreateVault)?; + create_dir_write_only(parent_path).map_err(CreateVault)?; } std::fs::write(real_path.clone(), payload.as_bytes()) .map_err(|e| VaultError::WriteVault(e, filename))?; - self.to_virtual_path(&real_path, &particle.id) + self.to_virtual_path(current_peer_id, particle, &real_path) } pub fn cat( &self, + current_peer_id: PeerId, particle: &ParticleParams, virtual_path: &Path, ) -> Result { - let real_path = self.to_real_path(virtual_path, &particle.id)?; + let real_path = self.to_real_path(current_peer_id, particle, virtual_path)?; let contents = std::fs::read_to_string(real_path) .map_err(|e| VaultError::ReadVault(e, virtual_path.to_path_buf()))?; @@ -105,15 +133,21 @@ impl ParticleVault { pub fn cat_slice( &self, + current_peer_id: PeerId, particle: &ParticleParams, virtual_path: &Path, ) -> Result, VaultError> { - let real_path = self.to_real_path(virtual_path, &particle.id)?; + let real_path = self.to_real_path(current_peer_id, particle, virtual_path)?; std::fs::read(real_path).map_err(|e| VaultError::ReadVault(e, virtual_path.to_path_buf())) } - pub async fn cleanup(&self, particle_id: &str) -> Result<(), VaultError> { - let path = self.particle_vault(particle_id); + pub async fn cleanup( + &self, + peer_id: PeerId, + particle_id: &str, + particle_token: &str, + ) -> Result<(), VaultError> { + let path = self.real_particle_vault(peer_id, particle_id, particle_token); match tokio::fs::remove_dir_all(&path).await { Ok(_) => Ok(()), // ignore NotFound @@ -126,9 +160,14 @@ impl ParticleVault { /// Converts real path in `vault_dir` to virtual path with `VIRTUAL_PARTICLE_VAULT_PREFIX`. /// Virtual path looks like `/tmp/vault//`. - fn to_virtual_path(&self, path: &Path, particle_id: &str) -> Result { - let virtual_prefix = self.virtual_particle_vault(particle_id); - let real_prefix = self.particle_vault(particle_id); + fn to_virtual_path( + &self, + current_peer_id: PeerId, + particle: &ParticleParams, + path: &Path, + ) -> Result { + let virtual_prefix = self.virtual_particle_vault(&particle.id, &particle.token); + let real_prefix = self.real_particle_vault(current_peer_id, &particle.id, &particle.token); let rest = path .strip_prefix(&real_prefix) .map_err(|e| WrongVault(Some(e), path.to_path_buf(), real_prefix))?; @@ -140,21 +179,27 @@ impl ParticleVault { /// Support full paths to the file in the vault starting this the prefix as well as relative paths /// inside the vault. /// For example, `some/file.txt` is valid and will be resolved to `REAL_PARTICLE_VAULT_PREFIX/some/file.txt`. - fn to_real_path(&self, path: &Path, particle_id: &str) -> Result { - let rest = if path.has_root() { + fn to_real_path( + &self, + current_peer_id: PeerId, + particle: &ParticleParams, + virtual_path: &Path, + ) -> Result { + let rest = if virtual_path.has_root() { // If path starts with the `/` then we consider it a full path containing the virtual vault prefix - let virtual_prefix = self.virtual_particle_vault(particle_id); - path.strip_prefix(&virtual_prefix) - .map_err(|e| WrongVault(Some(e), path.to_path_buf(), virtual_prefix.clone()))? + let virtual_prefix = self.virtual_particle_vault(&particle.id, &particle.token); + virtual_path.strip_prefix(&virtual_prefix).map_err(|e| { + WrongVault(Some(e), virtual_path.to_path_buf(), virtual_prefix.clone()) + })? } else { // Otherwise we consider it a relative path inside the vault - path + virtual_path }; - let real_prefix = self.particle_vault(particle_id); + let real_prefix = self.real_particle_vault(current_peer_id, &particle.id, &particle.token); let real_path = real_prefix.join(rest); let resolved_path = real_path .canonicalize() - .map_err(|e| VaultError::NotFound(e, path.to_path_buf()))?; + .map_err(|e| VaultError::NotFound(e, virtual_path.to_path_buf()))?; // Check again after normalization that the path leads to the real particle vault if resolved_path.starts_with(&real_prefix) { Ok(resolved_path) @@ -163,16 +208,20 @@ impl ParticleVault { } } - /// Map `vault_dir` to `/tmp/vault` inside the service. + /// Map `vault_dir/$current-peer-id` to `/tmp/vault` inside the service. /// Particle File Vaults will be available as `/tmp/vault/$particle_id` - pub fn inject_vault(&self, module: &mut ModuleDescriptor) -> eyre::Result<()> { + pub fn inject_vault( + &self, + current_peer_id: PeerId, + module: &mut ModuleDescriptor, + ) -> eyre::Result<()> { let wasi = module .config .wasi .as_mut() .ok_or(eyre!("Could not inject vault into empty WASI config"))?; - let vault_dir = self.vault_dir.to_path_buf(); + let vault_dir = self.real_worker_particle_vault(current_peer_id); wasi.mapped_dirs .insert(VIRTUAL_PARTICLE_VAULT_PREFIX.into(), vault_dir); diff --git a/particle-modules/Cargo.toml b/particle-modules/Cargo.toml index 4a7945ad89..294d5439e3 100644 --- a/particle-modules/Cargo.toml +++ b/particle-modules/Cargo.toml @@ -11,6 +11,7 @@ particle-args = { workspace = true } json-utils = { workspace = true } fs-utils = { workspace = true } service-modules = { workspace = true } +fluence-libp2p = { workspace = true } marine-it-parser = { workspace = true } fluence-app-service = { workspace = true } diff --git a/particle-modules/src/modules.rs b/particle-modules/src/modules.rs index a1223bdc3f..5c8ad0eb53 100644 --- a/particle-modules/src/modules.rs +++ b/particle-modules/src/modules.rs @@ -29,6 +29,7 @@ use marine_it_parser::module_interface; use parking_lot::RwLock; use serde_json::{json, Value as JValue}; +use fluence_libp2p::PeerId; use particle_args::JError; use particle_execution::{ParticleParams, ParticleVault}; use service_modules::{ @@ -98,10 +99,12 @@ impl ModuleRepository { pub fn load_module_config_from_vault( vault: &ParticleVault, + // TODO: refactor this out of this crate + current_peer_id: PeerId, config_path: String, particle: ParticleParams, ) -> Result { - let config = vault.cat_slice(&particle, Path::new(&config_path))?; + let config = vault.cat_slice(current_peer_id, &particle, Path::new(&config_path))?; serde_json::from_slice(&config) .map_err(|err| IncorrectVaultModuleConfig { config_path, err }) } @@ -121,11 +124,13 @@ impl ModuleRepository { pub fn add_module_from_vault( &self, vault: &ParticleVault, + // TODO: refactor this out of this crate + current_peer_id: PeerId, module_path: String, config: TomlMarineNamedModuleConfig, particle: ParticleParams, ) -> Result { - let module = vault.cat_slice(&particle, Path::new(&module_path))?; + let module = vault.cat_slice(current_peer_id, &particle, Path::new(&module_path))?; // copy module & config to module_dir let hash = self.add_module(module, config)?; diff --git a/particle-services/src/app_services.rs b/particle-services/src/app_services.rs index 083464aaf8..9ae6b85908 100644 --- a/particle-services/src/app_services.rs +++ b/particle-services/src/app_services.rs @@ -433,7 +433,11 @@ impl ParticleAppServices { // TODO: move particle vault creation to aquamarine::particle_functions if create_vault { - self.vault.create(&particle)?; + self.vault.create( + self.scopes.to_peer_id(particle.peer_scope), + &particle.id, + &particle.token, + )?; } let call_parameters_worker_id = self.scopes.to_peer_id(peer_scope); @@ -935,7 +939,11 @@ impl ParticleAppServices { ) -> Result>, ServiceError> { let creation_start_time = Instant::now(); let service = self - .create_app_service(blueprint_id.clone(), service_id.clone()) + .create_app_service( + self.scopes.to_peer_id(peer_scope), + blueprint_id.clone(), + service_id.clone(), + ) .await .inspect_err(|_| { if let Some(metrics) = self.metrics.as_ref() { @@ -1048,6 +1056,7 @@ impl ParticleAppServices { async fn create_app_service( &self, + current_peer_id: PeerId, blueprint_id: String, service_id: String, ) -> Result { @@ -1070,10 +1079,13 @@ impl ParticleAppServices { let mut modules_config = self.modules.resolve_blueprint(&blueprint_id)?; + // Create Particle File Vault for Worker + self.vault.initialize_worker(current_peer_id)?; + for module in modules_config.iter_mut() { self.inject_default_wasi(module); // SAFETY: set wasi to Some in the code before calling inject_vault - self.vault.inject_vault(module).unwrap(); + self.vault.inject_vault(current_peer_id, module).unwrap(); self.inject_persistent_dirs(module, persistent_dir.as_path()) .await?; self.inject_ephemeral_dirs(module, ephemeral_dir.as_path())