diff --git a/particle-builtins/src/builtins.rs b/particle-builtins/src/builtins.rs index 257565d0b0..7f2441af90 100644 --- a/particle-builtins/src/builtins.rs +++ b/particle-builtins/src/builtins.rs @@ -51,7 +51,6 @@ use crate::debug::fmt_custom_services; use crate::error::HostClosureCallError; use crate::error::HostClosureCallError::{DecodeBase58, DecodeUTF8}; use crate::func::{binary, unary}; -use crate::identify::NodeInfo; use crate::outcome::{ok, wrap, wrap_unit}; use crate::{json, math}; @@ -87,8 +86,6 @@ pub struct Builtins { pub modules: ModuleRepository, pub services: ParticleAppServices, - pub node_info: NodeInfo, - #[derivative(Debug(format_with = "fmt_custom_services"))] pub custom_services: RwLock>, @@ -105,7 +102,6 @@ where pub fn new( connectivity: C, script_storage: ScriptStorageApi, - node_info: NodeInfo, config: ServicesConfig, services_metrics: ServicesMetrics, key_manager: KeyManager, @@ -135,7 +131,6 @@ where local_peer_id, modules, services, - node_info, particles_vault_dir, custom_services: <_>::default(), key_manager, @@ -191,7 +186,6 @@ where #[rustfmt::skip] match (args.service_id.as_str(), args.function_name.as_str()) { - ("peer", "identify") => ok(json!(self.node_info)), ("peer", "timestamp_ms") => ok(json!(now_ms() as u64)), ("peer", "timestamp_sec") => ok(json!(now_sec())), ("peer", "is_connected") => wrap(self.is_connected(args).await), diff --git a/particle-builtins/src/identify.rs b/particle-builtins/src/identify.rs index 3851d69545..4555713f98 100644 --- a/particle-builtins/src/identify.rs +++ b/particle-builtins/src/identify.rs @@ -22,4 +22,6 @@ pub struct NodeInfo { pub external_addresses: Vec, pub node_version: &'static str, pub air_version: &'static str, + pub spell_version: String, + pub allowed_binaries: Vec, } diff --git a/particle-node/Cargo.toml b/particle-node/Cargo.toml index 93074a7145..85572c4a9d 100644 --- a/particle-node/Cargo.toml +++ b/particle-node/Cargo.toml @@ -19,6 +19,7 @@ sorcerer = { workspace = true } dhat = { version = "0.3.2", optional = true } +serde_json = { workspace = true } fluence-libp2p = { workspace = true } server-config = { workspace = true } config-utils = { workspace = true } diff --git a/particle-node/src/builtins.rs b/particle-node/src/builtins.rs new file mode 100644 index 0000000000..6908842f7a --- /dev/null +++ b/particle-node/src/builtins.rs @@ -0,0 +1,36 @@ +/* + * Copyright 2023 Fluence Labs Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use futures::FutureExt; +use particle_builtins::{ok, CustomService, NodeInfo}; +use particle_execution::ServiceFunction; +use serde_json::json; + +pub fn make_peer_builtin(node_info: NodeInfo) -> (String, CustomService) { + ( + "peer".to_string(), + CustomService::new( + vec![("identify", make_peer_identify_closure(node_info))], + None, + ), + ) +} +fn make_peer_identify_closure(node_info: NodeInfo) -> ServiceFunction { + ServiceFunction::Immut(Box::new(move |_args, _params| { + let node_info = node_info.clone(); + async move { ok(json!(node_info)) }.boxed() + })) +} diff --git a/particle-node/src/lib.rs b/particle-node/src/lib.rs index ddd6dad9c9..1070bab0a3 100644 --- a/particle-node/src/lib.rs +++ b/particle-node/src/lib.rs @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +#![feature(extend_one)] #![feature(try_blocks)] #![feature(drain_filter)] #![feature(ip)] @@ -28,6 +29,7 @@ unreachable_patterns )] +mod builtins; mod connectivity; mod dispatcher; mod effectors; diff --git a/particle-node/src/node.rs b/particle-node/src/node.rs index a6d803c441..192d084d5c 100644 --- a/particle-node/src/node.rs +++ b/particle-node/src/node.rs @@ -52,6 +52,7 @@ use spell_event_bus::bus::SpellEventBus; use tokio::sync::{mpsc, oneshot}; use tokio::task; +use crate::builtins::make_peer_builtin; use crate::dispatcher::Dispatcher; use crate::effectors::Effectors; use crate::metrics::start_metrics_endpoint; @@ -192,9 +193,14 @@ impl Node { ) }; + let allowed_binaries = services_config + .allowed_binaries + .iter() + .map(|s| s.to_string_lossy().to_string()) + .collect::<_>(); + let builtins = Arc::new(Self::builtins( connectivity.clone(), - config.external_addresses(), services_config, script_storage_api, services_metrics, @@ -244,7 +250,7 @@ impl Node { let (spell_event_bus, spell_event_bus_api, spell_events_receiver) = SpellEventBus::new(sources); - let (sorcerer, spell_service_functions) = Sorcerer::new( + let (sorcerer, mut custom_service_functions, spell_version) = Sorcerer::new( builtins.services.clone(), builtins.modules.clone(), aquamarine_api.clone(), @@ -253,7 +259,16 @@ impl Node { key_manager.clone(), ); - spell_service_functions.into_iter().for_each( + let node_info = NodeInfo { + external_addresses: config.external_addresses(), + node_version: env!("CARGO_PKG_VERSION"), + air_version: air_interpreter_wasm::VERSION, + spell_version, + allowed_binaries, + }; + custom_service_functions.extend_one(make_peer_builtin(node_info)); + + custom_service_functions.into_iter().for_each( move |( service_id, CustomService { @@ -319,22 +334,14 @@ impl Node { pub fn builtins( connectivity: Connectivity, - external_addresses: Vec, services_config: ServicesConfig, script_storage_api: ScriptStorageApi, services_metrics: ServicesMetrics, key_manager: KeyManager, ) -> Builtins { - let node_info = NodeInfo { - external_addresses, - node_version: env!("CARGO_PKG_VERSION"), - air_version: air_interpreter_wasm::VERSION, - }; - Builtins::new( connectivity, script_storage_api, - node_info, services_config, services_metrics, key_manager, diff --git a/sorcerer/src/sorcerer.rs b/sorcerer/src/sorcerer.rs index 08f4fa1f1b..01105264d7 100644 --- a/sorcerer/src/sorcerer.rs +++ b/sorcerer/src/sorcerer.rs @@ -59,8 +59,8 @@ impl Sorcerer { config: ResolvedConfig, spell_event_bus_api: SpellEventBusApi, key_manager: KeyManager, - ) -> (Self, HashMap) { - let spell_storage = + ) -> (Self, HashMap, String) { + let (spell_storage, spell_version) = SpellStorage::create(&config.dir_config.spell_base_dir, &services, &modules) .expect("Spell storage creation"); @@ -76,7 +76,7 @@ impl Sorcerer { let mut builtin_functions = sorcerer.make_spell_builtins(); builtin_functions.extend_one(sorcerer.make_worker_builtin()); - (sorcerer, builtin_functions) + (sorcerer, builtin_functions, spell_version) } async fn resubscribe_spells(&self) { diff --git a/spell-storage/src/storage.rs b/spell-storage/src/storage.rs index f57e09dc32..0b82a86097 100644 --- a/spell-storage/src/storage.rs +++ b/spell-storage/src/storage.rs @@ -31,9 +31,9 @@ impl SpellStorage { spells_base_dir: &Path, services: &ParticleAppServices, modules: &ModuleRepository, - ) -> eyre::Result { + ) -> eyre::Result<(Self, String)> { let spell_config_path = spell_config_path(spells_base_dir); - let spell_blueprint_id = if spell_config_path.exists() { + let (spell_blueprint_id, spell_version) = if spell_config_path.exists() { let cfg = TomlMarineConfig::load(spell_config_path)?; Self::load_spell_service(cfg, spells_base_dir, modules)? } else { @@ -41,13 +41,16 @@ impl SpellStorage { }; let registered_spells = Self::restore_spells(services, modules); - Ok(Self { - spell_blueprint_id, - registered_spells: Arc::new(RwLock::new(registered_spells)), - }) + Ok(( + Self { + spell_blueprint_id, + registered_spells: Arc::new(RwLock::new(registered_spells)), + }, + spell_version, + )) } - fn load_spell_service_from_crate(modules: &ModuleRepository) -> eyre::Result { + fn load_spell_service_from_crate(modules: &ModuleRepository) -> eyre::Result<(String, String)> { use fluence_spell_distro::{modules as spell_modules, CONFIG}; log::info!( @@ -70,15 +73,19 @@ impl SpellStorage { hashes.push(Dependency::Hash(hash)) } - Ok(modules.add_blueprint(AddBlueprint::new("spell".to_string(), hashes))?) + Ok(( + modules.add_blueprint(AddBlueprint::new("spell".to_string(), hashes))?, + fluence_spell_distro::VERSION.to_string(), + )) } fn load_spell_service( cfg: TomlMarineConfig, spells_base_dir: &Path, modules: &ModuleRepository, - ) -> eyre::Result { + ) -> eyre::Result<(String, String)> { let mut hashes = Vec::new(); + let mut versions = Vec::new(); for config in cfg.module { let load_from = config .load_from @@ -89,10 +96,16 @@ impl SpellStorage { let module_path = spells_base_dir.join(load_from); let module = load_module_by_path(module_path.as_ref())?; let hash = modules.add_module(module, config)?; + let hex = hash.to_hex(); + let hex = hex.as_ref(); + versions.push(String::from(&hex[..8])); hashes.push(Dependency::Hash(hash)); } - - Ok(modules.add_blueprint(AddBlueprint::new("spell".to_string(), hashes))?) + let spell_disk_version = format!("wasm hashes {}", versions.join(" ")); + Ok(( + modules.add_blueprint(AddBlueprint::new("spell".to_string(), hashes))?, + spell_disk_version, + )) } fn restore_spells(